Repository: ocaml-multicore/picos
Pull request #255 (2024-08-31 18:53:40)

Split package
This splits the picos package into several smaller packages, roughly:

- `picos` -> (`backoff`, `thread-local-storage`)
  - The core interface 
- `picos_aux` -> (`backoff`, `multicore-magic`)
  - Mostly lock-free data structures (these might be sent to Saturn in the future)
- `picos_std` -> `picos`(, `backoff`, `multicore-magic`)
  - Scheduler agnostic libraries
- `picos_stdio` -> `picos_std`, `picos_aux`, and `unix`(, `mtime`, `psq`)
- `picos_stdio_cohttp` -> `picos_stdio`(, `cohttp`, `fmt`, `uri`)
- `picos_mux` -> `picos_std`, `picos_aux`, and _optionally_ on `picos_stdio`
  - Schedulers
- `picos_lwt` -> `picos_std`, `picos_aux`(, `lwt`)
- `picos_meta` -> all of the above
  - Integration (and other) tests
  - Makes it easy to simplify the dependencies of other packages

The above changes should make it easier for people to depend only on the packages they need.   A single package with dependencies to `lwt` and `cohttp` is just too heavy.

This also removes the `Exn_bt` module and type entirely.  It is impossible to have such a module in OCaml.

Due to the split to multiple packages the structure of the documentation has also been updated.  Currently odoc doesn't support references between packages, which, unfortunately, downgrades the quality of the documentation.

Finally, it has become clear that Miou is not compatible with Picos.  There are several reasons for this:
- Miou wants core Picos to be a single file that could be trivially copied and built without tooling such as dune and opam
- Miou wants to remove `Cancel_after`, because it is, according to Miou's author, impossible to support in MirageOS
- Miou wants to remove `Fiber.t` (replacing it with an integer id)
- Miou finds the implementation of `Fiber.FLS` unacceptable, because it uses the `Obj` module to store heterogenous values in an array (similar to what `Stdlib.Domain.DLS` does)
- Miou is opposed to use of thread-local-storage, which is currently used on OCaml 4 and will likely be used in the future to significantly speed up `Fiber.current` and `Fiber.spawn` (note that TLS also uses `Obj` like `Stdlib.Domain.DLS`)
- Miou finds the governance of Picos unacceptable
- It was also unacceptable for Miou for Picos to include the `Exn_bt` module(, library, and package) &mdash; those have been removed

Thanks to c-cube for starting the work on this in #249!


Patch:
diff --git a/README.md b/README.md
index 460355b..2031d07 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-[API reference](https://ocaml-multicore.github.io/picos/doc/picos/index.html)
-&middot;
+[API reference](https://ocaml-multicore.github.io/picos/doc/index.html) &middot;
 [Benchmarks](https://bench.ci.dev/ocaml-multicore/picos/branch/main?worker=pascal&image=bench.Dockerfile)
 &middot;
 [Stdlib Benchmarks](https://bench.ci.dev/ocaml-multicore/multicore-bench/branch/main?worker=pascal&image=bench.Dockerfile)
@@ -34,9 +33,8 @@ models such as
 by decoupling such elements from each other.
 
 Picos comes with a
-[reference manual](https://ocaml-multicore.github.io/picos/doc/picos/index.html)
-and many
-[sample libraries](https://ocaml-multicore.github.io/picos/doc/picos/index.html#libraries).
+[reference manual](https://ocaml-multicore.github.io/picos/doc/index.html) and
+many sample libraries.
 
 ⚠️ Please note that Picos is still considered experimental and unstable.
 
@@ -90,7 +88,7 @@ ability to cancel fibers in case of errors is crucial for the implementation of
 practical concurrent programming models.
 
 Consider the following characteristic
-[example](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/index.html#understanding-cancelation):
+[example](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html#understanding-cancelation):
 
 ```ocaml skip
 Mutex.protect mutex begin fun () ->
@@ -155,25 +153,24 @@ Here is an extract from the signature of the
 <!--
 ```ocaml
 # open Picos
-# open Picos_finally
-# open Picos_structured
-# open Picos_sync
+# open Picos_std_finally
+# open Picos_std_structured
+# open Picos_std_sync
 ```
 -->
 
 ```ocaml skip
 type t
 val create : unit -> t
-val await : t -> Exn_bt.t option
+val await : t -> (exn * Printexc.raw_backtrace) option
 val signal : t -> unit
 val on_signal : (* for schedulers *)
 ```
 
 The idea is that a fiber may create a trigger, insert it into some shared data
 structure, and then call `await` to ask the scheduler to suspend the fiber until
-something signals the trigger. The `Exn_bt.t` type represents an exception with
-a backtrace. When `await` returns an exception with a backtrace it means that
-the fiber has been canceled.
+something signals the trigger. When `await` returns an exception with a
+backtrace it means that the fiber has been canceled.
 
 As an example, let's consider the implementation of an `Ivar` or incremental or
 single-assignment variable:
@@ -235,9 +232,9 @@ let rec read t =
     if Atomic.compare_and_set t before after then
       match Trigger.await trigger with
       | None -> read t
-      | Some exn_bt ->
+      | Some (exn, bt) ->
         cleanup t trigger; (* ! *)
-        Exn_bt.raise exn_bt
+        Printexc.raise_with_backtrace exn bt
     else
       read t
 ```
@@ -296,7 +293,7 @@ val try_attach : 'a t -> Trigger.t -> bool
 val detach : 'a t -> Trigger.t -> unit
 
 val try_return : 'a t -> 'a -> bool
-val try_cancel : 'a t -> Exn_bt.t -> bool
+val try_cancel : 'a t -> exn -> Printexc.raw_backtrace -> bool
 
 val check : 'a t -> unit
 val await : 'a t -> 'a
@@ -429,8 +426,8 @@ let fork t action =
       match action () with
       | () -> decr t
       | exception exn ->
-          let exn_bt = Exn_bt.get exn in
-          Computation.cancel t.inner exn_bt;
+          let bt = Printexc.get_raw_backtrace () in
+          Computation.cancel t.inner exn bt;
           decr t
     in
     let fiber =
@@ -476,10 +473,10 @@ let run body =
   with
   | () -> join t outer canceler fiber
   | exception exn ->
-      let exn_bt = Exn_bt.get exn in
-      Computation.cancel t.inner exn_bt;
+      let bt = Printexc.get_raw_backtrace () in
+      Computation.cancel t.inner exn bt;
       join t outer canceler fiber;
-      Exn_bt.raise exn_bt
+      Printexc.raise_with_backtrace exn bt
 ```
 
 The `Computation.attach_canceler` operation attaches a special trigger to
@@ -542,7 +539,7 @@ examples presented here.
 For example, a library provides an operation to run a block with a timeout on
 the current fiber. One could use it with `Ivar.read` to implement a read
 operation
-[with a timeout](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/Control/index.html#val-terminate_after):
+[with a timeout](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Control/index.html#val-terminate_after):
 
 ```ocaml
 let read_in ~seconds ivar =
@@ -576,10 +573,11 @@ of five effects:
 
 ```ocaml version>=5.0.0
 type _ Effect.t +=
-  | Await : Trigger.t -> Exn_bt.t option Effect.t
+  | Await : Trigger.t -> (exn * Printexc.raw_backtrace) option Effect.t
   | Cancel_after : {
       seconds : float;
-      exn_bt : Exn_bt.t;
+      exn: exn;
+      bt : Printexc.raw_backtrace;
       computation : 'a Computation.t;
     }
       -> unit Effect.t
@@ -601,7 +599,7 @@ implemented against the Picos interface should not assume any particular
 scheduling.
 
 Picos actually comes with
-[a randomized multithreaded scheduler](https://ocaml-multicore.github.io/picos/doc/picos/Picos_randos/index.html),
+[a randomized multithreaded scheduler](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_randos/index.html),
 that, after handling any of the effects, picks the next ready fiber randomly. It
 has proven to be useful for testing that abstractions implemented in Picos do
 not make invalid scheduling assumptions.
@@ -616,16 +614,16 @@ specific schedulers.
 We have an experimental design and implementation of the core Picos interface as
 illustrated in the previous section. We have also created several _Picos
 compatible_
-[sample schedulers](https://ocaml-multicore.github.io/picos/doc/picos/index.html#sample-schedulers).
+[sample schedulers](https://ocaml-multicore.github.io/picos/doc/picos_std/index.html#schedulers).
 A scheduler, in this context, just multiplexes fibers to run on one or more
 system level threads. We have also created some sample higher-level
-[scheduler agnostic libraries](https://ocaml-multicore.github.io/picos/doc/picos/index.html#scheduler-agnostic-libraries)
+[scheduler agnostic libraries](https://ocaml-multicore.github.io/picos/doc/picos_std/index.html#scheduler-agnostic-libraries)
 _Implemented in Picos_. These libraries include
-[a library for resource management](https://ocaml-multicore.github.io/picos/doc/picos/Picos_finally/index.html),
-[a library for structured concurrency](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/index.html),
-[a library of synchronization primitives](https://ocaml-multicore.github.io/picos/doc/picos/Picos_sync/index.html),
+[a library for resource management](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html),
+[a library for structured concurrency](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html),
+[a library of synchronization primitives](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_sync/index.html),
 and
-[an asynchronous I/O library](https://ocaml-multicore.github.io/picos/doc/picos/Picos_stdio/index.html).
+[an asynchronous I/O library](https://ocaml-multicore.github.io/picos/doc/picos_stdio/Picos_stdio/index.html).
 The synchronization library and the I/O library intentionally mimic libraries
 that come with the OCaml distribution. All of the libraries work with all of the
 schedulers and all of these _elements_ are interoperable and entirely opt-in.
@@ -638,7 +636,7 @@ library. A basic single threaded scheduler implementation requires only about
 couple of hundred LOC. The scheduler agnostic libraries are similarly small.
 
 Here is an
-[example](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/index.html#a-simple-echo-server-and-clients)
+[example](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/index.html#a-simple-echo-server-and-clients)
 of a concurrent echo server using the scheduler agnostic libraries provided as
 samples:
 
@@ -664,27 +662,27 @@ let run_server server_fd =
 ```
 
 The
-[`Unix`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_stdio/Unix/index.html)
+[`Unix`](https://ocaml-multicore.github.io/picos/doc/picos_stdio/Picos_stdio/Unix/index.html)
 module is provided by the I/O library. The operations on file descriptors on
 that module, such as `accept`, `read`, and `write`, use the Picos interface to
 suspend fibers allowing other fibers to run while waiting for I/O. The
-[`Flock`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/Flock/index.html)
+[`Flock`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html)
 module comes from the structured concurrency library. A call of
-[`join_after`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/Flock/index.html#val-join_after)
+[`join_after`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html#val-join_after)
 returns only after all the fibers
-[`fork`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_structured/Flock/index.html#val-fork)ed
+[`fork`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_structured/Flock/index.html#val-fork)ed
 into the flock have terminated. If the main fiber of the flock is canceled, or
 any fiber within the flock raises an unhandled exception, all the fibers within
 the flock will be canceled and an exception will be raised on the main fiber of
 the flock. The
-[`let@`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_finally/index.html#val-let@),
-[`finally`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_finally/index.html#val-instantiate),
+[`let@`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-let@),
+[`finally`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-instantiate),
 and
-[`move`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_finally/index.html#val-move)
+[`move`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-move)
 operations come from the resource management library and allow dealing with
 resources in a leak-free manner. The responsibility to close the `client_fd`
 socket is
-[`move`](https://ocaml-multicore.github.io/picos/doc/picos/Picos_finally/index.html#val-move)d
+[`move`](https://ocaml-multicore.github.io/picos/doc/picos_std/Picos_std_finally/index.html#val-move)d
 from the main server fiber to a fiber forked to handle that client.
 
 We should emphasize that the above is just an example. The Picos interface
@@ -698,7 +696,7 @@ and with other concurrent abstractions.
 
 Finally, an interesting demonstration that Picos really fundamentally is an
 interface is
-[a prototype _Picos compatible_ direct style interface to Lwt](https://ocaml-multicore.github.io/picos/doc/picos/Picos_lwt/index.html).
+[a prototype _Picos compatible_ direct style interface to Lwt](https://ocaml-multicore.github.io/picos/doc/picos_lwt/Picos_lwt/index.html).
 The implementation uses shallow effect handlers and defers all scheduling
 decisions to Lwt. Running a program with the scheduler returns a Lwt promise.
 
@@ -778,9 +776,10 @@ concurrent programming models.
 
 It should be
 technically<sup>[\*](https://www.youtube.com/watch?v=hou0lU8WMgo)</sup> possible
-to
+for all the previously mentioned libraries, except
+[Miou](https://github.com/robur-coop/miou), to
 
-1. make all of the previously mentioned libraries
+1. be made
    [Picos compatible](https://ocaml-multicore.github.io/picos/doc/picos/index.html#picos-compatible),
    i.e. to handle the Picos effects, and
 2. have their elements
@@ -788,5 +787,5 @@ to
    i.e. to make them usable on other Picos-compatible schedulers.
 
 Please read
-[the reference manual](https://ocaml-multicore.github.io/picos/doc/picos/index.html)
+[the reference manual](https://ocaml-multicore.github.io/picos/doc/index.html)
 for further information.
diff --git a/bench/bench_binaries.ml b/bench/bench_binaries.ml
index 4b1cd65..abaf70f 100644
--- a/bench/bench_binaries.ml
+++ b/bench/bench_binaries.ml
@@ -1,4 +1,4 @@
-open Picos_finally
+open Picos_std_finally
 open Multicore_bench
 
 let lib_suffix = if Sys.os_type == "Win32" then "lib" else "a"
@@ -7,31 +7,32 @@ let paths =
   let lib ?(subs = []) name =
     let path base name = "lib/" ^ base ^ "/" ^ name ^ "." ^ lib_suffix in
     ( name,
-      path name name
+      path name (String.map (function '.' -> '_' | c -> c) name)
       :: List.map (fun sub -> path (name ^ "/" ^ sub) (name ^ "_" ^ sub)) subs
     )
   in
   [
     lib "picos" ~subs:[ "bootstrap"; "ocaml5"; "ocaml4" ];
-    lib "picos_domain";
-    lib "picos_exn_bt";
-    lib "picos_fd";
-    lib "picos_fifos";
-    lib "picos_finally";
-    lib "picos_htbl";
+    lib "picos.domain";
+    lib "picos.thread";
+    lib "picos_aux.htbl";
+    lib "picos_aux.mpmcq";
+    lib "picos_aux.mpscq";
+    lib "picos_aux.rc";
     lib "picos_lwt";
-    lib "picos_lwt_unix";
-    lib "picos_mpmcq";
-    lib "picos_mpscq";
-    lib "picos_multififos";
-    lib "picos_randos";
-    lib "picos_rc";
-    lib "picos_select";
+    lib "picos_lwt.unix";
+    lib "picos_mux.fifo";
+    lib "picos_mux.multififo";
+    lib "picos_mux.random";
+    lib "picos_mux.thread";
+    lib "picos_std.event";
+    lib "picos_std.finally";
+    lib "picos_std.structured";
+    lib "picos_std.sync";
     lib "picos_stdio";
-    lib "picos_structured";
-    lib "picos_sync";
-    lib "picos_thread";
-    lib "picos_threaded";
+    lib "picos_stdio.fd";
+    lib "picos_stdio.select";
+    lib "picos_stdio_cohttp";
   ]
 
 let run_suite ~budgetf:_ =
diff --git a/bench/bench_bounded_q.ml b/bench/bench_bounded_q.ml
index 8a3723b..d85b03b 100644
--- a/bench/bench_bounded_q.ml
+++ b/bench/bench_bounded_q.ml
@@ -1,6 +1,6 @@
 open Multicore_bench
-open Picos_structured
-open Picos_sync
+open Picos_std_structured
+open Picos_std_sync
 module Queue = Stdlib.Queue
 
 let is_ocaml4 = String.starts_with ~prefix:"4." Sys.ocaml_version
diff --git a/bench/bench_cancel_after.ml b/bench/bench_cancel_after.ml
index 7896275..3846382 100644
--- a/bench/bench_cancel_after.ml
+++ b/bench/bench_cancel_after.ml
@@ -1,8 +1,8 @@
 open Multicore_bench
-open Picos_structured
+open Picos_std_structured
 open Picos
 
-let exit_exn_bt = Exn_bt.get_callstack 0 Exit
+let empty_bt = Printexc.get_callstack 0
 let seconds = 0.000_000_001 (* 1 ns *)
 
 let run_round_trip ~budgetf ~n_domains () =
@@ -19,7 +19,7 @@ let run_round_trip ~budgetf ~n_domains () =
         let rec loop n =
           if 0 < n then begin
             let computation = Computation.create ~mode:`LIFO () in
-            Computation.cancel_after computation ~seconds exit_exn_bt;
+            Computation.cancel_after computation ~seconds Exit empty_bt;
             Computation.wait computation;
             loop (n - 1)
           end
@@ -65,7 +65,7 @@ let run_async ~budgetf ~n_domains () =
           if 0 < n then begin
             let computation = Computation.create ~mode:`LIFO () in
             Queue.push computation queue;
-            Computation.cancel_after computation ~seconds exit_exn_bt;
+            Computation.cancel_after computation ~seconds Exit empty_bt;
             loop (n - 1)
           end
           else work ()
diff --git a/bench/bench_fib.ocaml5.ml b/bench/bench_fib.ocaml5.ml
index 526389d..e444671 100644
--- a/bench/bench_fib.ocaml5.ml
+++ b/bench/bench_fib.ocaml5.ml
@@ -17,14 +17,16 @@ let rec exp_fib i =
 
 let ratio = (1.0 +. Float.sqrt 5.0) *. 0.5
 
+module Randos = Picos_mux_random
+
 let run_one_randos ~budgetf ~n_domains ~n () =
   let context = ref (Obj.magic ()) in
 
-  let before _ = context := Picos_randos.context () in
+  let before _ = context := Randos.context () in
   let init _ = !context in
   let work i context =
-    if i <> 0 then Picos_randos.runner_on_this_thread context
-    else ignore @@ Picos_randos.run ~context @@ fun () -> exp_fib n
+    if i <> 0 then Randos.runner_on_this_thread context
+    else ignore @@ Randos.run ~context @@ fun () -> exp_fib n
   in
 
   let config =
@@ -37,14 +39,16 @@ let run_one_randos ~budgetf ~n_domains ~n () =
        ~n:(Float.to_int (Float.of_int (lin_fib n) *. ratio))
        ~singular:"spawn" ~config
 
+module Multififos = Picos_mux_multififo
+
 let run_one_multififos ~budgetf ~n_domains ~n () =
   let context = ref (Obj.magic ()) in
 
-  let before _ = context := Picos_multififos.context () in
+  let before _ = context := Multififos.context () in
   let init _ = !context in
   let work i context =
-    if i <> 0 then Picos_multififos.runner_on_this_thread context
-    else ignore @@ Picos_multififos.run ~context @@ fun () -> exp_fib n
+    if i <> 0 then Multififos.runner_on_this_thread context
+    else ignore @@ Multififos.run ~context @@ fun () -> exp_fib n
   in
 
   let config =
diff --git a/bench/bench_htbl.ml b/bench/bench_htbl.ml
index 82b00b1..49308e6 100644
--- a/bench/bench_htbl.ml
+++ b/bench/bench_htbl.ml
@@ -1,5 +1,5 @@
 open Multicore_bench
-module Htbl = Picos_htbl
+module Htbl = Picos_aux_htbl
 
 module Key = struct
   type t = int
diff --git a/bench/bench_memory.ml b/bench/bench_memory.ml
index 122103c..909d237 100644
--- a/bench/bench_memory.ml
+++ b/bench/bench_memory.ml
@@ -15,7 +15,7 @@ let run_suite ~budgetf:_ =
     [
       begin
         Scheduler.run @@ fun () ->
-        let open Picos_structured in
+        let open Picos_std_structured in
         Bundle.join_after ~on_return:`Terminate @@ fun bundle ->
         let n = 10_000 in
         let bytes =
@@ -36,7 +36,7 @@ let run_suite ~budgetf:_ =
       end;
       begin
         Scheduler.run @@ fun () ->
-        let open Picos_structured in
+        let open Picos_std_structured in
         Bundle.join_after ~on_return:`Terminate @@ fun bundle ->
         let n = 10_000 in
         let bytes =
@@ -57,7 +57,7 @@ let run_suite ~budgetf:_ =
       end;
       begin
         Scheduler.run @@ fun () ->
-        let open Picos_structured in
+        let open Picos_std_structured in
         Flock.join_after ~on_return:`Terminate @@ fun () ->
         let n = 10_000 in
         let bytes =
@@ -78,7 +78,7 @@ let run_suite ~budgetf:_ =
       end;
       begin
         Scheduler.run @@ fun () ->
-        let open Picos_structured in
+        let open Picos_std_structured in
         Flock.join_after ~on_return:`Terminate @@ fun () ->
         let n = 10_000 in
         let bytes =
@@ -100,7 +100,7 @@ let run_suite ~budgetf:_ =
       begin
         Scheduler.run @@ fun () ->
         let open Picos in
-        let open Picos_sync in
+        let open Picos_std_sync in
         let computation = Computation.create () in
         let (Packed main) = Fiber.get_computation (Fiber.current ()) in
         let _ = Computation.attach_canceler ~from:main ~into:computation in
@@ -121,7 +121,7 @@ let run_suite ~budgetf:_ =
           done;
           Fiber.yield ()
         in
-        Computation.cancel computation (Exn_bt.get_callstack 0 Exit);
+        Computation.cancel computation Exit (Printexc.get_callstack 0);
         Latch.decr latch;
         Latch.await latch;
         Metric.make ~metric:"memory used"
diff --git a/bench/bench_mpmcq.ml b/bench/bench_mpmcq.ml
index 82e7b40..4d9c09b 100644
--- a/bench/bench_mpmcq.ml
+++ b/bench/bench_mpmcq.ml
@@ -1,21 +1,17 @@
 open Multicore_bench
+module Mpmcq = Picos_aux_mpmcq
 
 let run_one_domain ~budgetf ?(n_msgs = 50 * Util.iter_factor) () =
-  let t = Picos_mpmcq.create ~padded:true () in
+  let t = Mpmcq.create ~padded:true () in
 
   let op push =
-    if push then Picos_mpmcq.push t 101
-    else
-      match Picos_mpmcq.pop_exn t with
-      | _ -> ()
-      | exception Picos_mpmcq.Empty -> ()
+    if push then Mpmcq.push t 101
+    else match Mpmcq.pop_exn t with _ -> () | exception Mpmcq.Empty -> ()
   in
 
   let init _ =
     assert (
-      match Picos_mpmcq.pop_exn t with
-      | _ -> false
-      | exception Picos_mpmcq.Empty -> true);
+      match Mpmcq.pop_exn t with _ -> false | exception Mpmcq.Empty -> true);
     Util.generate_push_and_pop_sequence n_msgs
   in
   let work _ bits = Util.Bits.iter op bits in
@@ -28,16 +24,14 @@ let run_one ~budgetf ~n_adders ~n_takers () =
 
   let n_msgs = 50 * Util.iter_factor in
 
-  let t = Picos_mpmcq.create ~padded:true () in
+  let t = Mpmcq.create ~padded:true () in
 
   let n_msgs_to_add = Countdown.create ~n_domains:n_adders () in
   let n_msgs_to_take = Countdown.create ~n_domains:n_takers () in
 
   let init _ =
     assert (
-      match Picos_mpmcq.pop_exn t with
-      | _ -> false
-      | exception Picos_mpmcq.Empty -> true);
+      match Mpmcq.pop_exn t with _ -> false | exception Mpmcq.Empty -> true);
     Countdown.non_atomic_set n_msgs_to_add n_msgs;
     Countdown.non_atomic_set n_msgs_to_take n_msgs
   in
@@ -47,7 +41,7 @@ let run_one ~budgetf ~n_adders ~n_takers () =
         let n = Countdown.alloc n_msgs_to_add ~domain_index:i ~batch:1000 in
         if 0 < n then begin
           for i = 1 to n do
-            Picos_mpmcq.push t i
+            Mpmcq.push t i
           done;
           work ()
         end
@@ -60,9 +54,9 @@ let run_one ~budgetf ~n_adders ~n_takers () =
         if 0 < n then
           let rec loop n =
             if 0 < n then begin
-              match Picos_mpmcq.pop_exn t with
+              match Mpmcq.pop_exn t with
               | _ -> loop (n - 1)
-              | exception Picos_mpmcq.Empty ->
+              | exception Mpmcq.Empty ->
                   Backoff.once Backoff.default |> ignore;
                   loop n
             end
diff --git a/bench/bench_mpscq.ml b/bench/bench_mpscq.ml
index 7d9777b..3f55277 100644
--- a/bench/bench_mpscq.ml
+++ b/bench/bench_mpscq.ml
@@ -1,21 +1,17 @@
 open Multicore_bench
+module Mpscq = Picos_aux_mpscq
 
 let run_one_domain ~budgetf ?(n_msgs = 50 * Util.iter_factor) () =
-  let t = Picos_mpscq.create ~padded:true () in
+  let t = Mpscq.create ~padded:true () in
 
   let op push =
-    if push then Picos_mpscq.push t 101
-    else
-      match Picos_mpscq.pop_exn t with
-      | _ -> ()
-      | exception Picos_mpscq.Empty -> ()
+    if push then Mpscq.push t 101
+    else match Mpscq.pop_exn t with _ -> () | exception Mpscq.Empty -> ()
   in
 
   let init _ =
     assert (
-      match Picos_mpscq.pop_exn t with
-      | _ -> false
-      | exception Picos_mpscq.Empty -> true);
+      match Mpscq.pop_exn t with _ -> false | exception Mpscq.Empty -> true);
     Util.generate_push_and_pop_sequence n_msgs
   in
   let work _ bits = Util.Bits.iter op bits in
@@ -29,15 +25,13 @@ let run_one ~budgetf ~n_adders () =
 
   let n_msgs = 200 * Util.iter_factor in
 
-  let t = Picos_mpscq.create ~padded:true () in
+  let t = Mpscq.create ~padded:true () in
 
   let n_msgs_to_add = Countdown.create ~n_domains:n_adders () in
 
   let init _ =
     assert (
-      match Picos_mpscq.pop_exn t with
-      | _ -> false
-      | exception Picos_mpscq.Empty -> true);
+      match Mpscq.pop_exn t with _ -> false | exception Mpscq.Empty -> true);
     Countdown.non_atomic_set n_msgs_to_add n_msgs
   in
   let work i () =
@@ -46,7 +40,7 @@ let run_one ~budgetf ~n_adders () =
         let n = Countdown.alloc n_msgs_to_add ~domain_index:i ~batch:1000 in
         if 0 < n then begin
           for i = 1 to n do
-            Picos_mpscq.push t i
+            Mpscq.push t i
           done;
           work ()
         end
@@ -55,9 +49,9 @@ let run_one ~budgetf ~n_adders () =
     else
       let rec loop n =
         if 0 < n then
-          match Picos_mpscq.pop_exn t with
+          match Mpscq.pop_exn t with
           | _ -> loop (n - 1)
-          | exception Picos_mpscq.Empty ->
+          | exception Mpscq.Empty ->
               Backoff.once Backoff.default |> ignore;
               loop n
       in
diff --git a/bench/bench_mutex.ml b/bench/bench_mutex.ml
index 866e60f..3af2e4e 100644
--- a/bench/bench_mutex.ml
+++ b/bench/bench_mutex.ml
@@ -1,6 +1,6 @@
 open Multicore_bench
-open Picos_structured
-open Picos_sync
+open Picos_std_structured
+open Picos_std_sync
 
 let is_ocaml4 = String.starts_with ~prefix:"4." Sys.ocaml_version
 
diff --git a/bench/bench_ref_mutex.ml b/bench/bench_ref_mutex.ml
index c565a95..1fef5ee 100644
--- a/bench/bench_ref_mutex.ml
+++ b/bench/bench_ref_mutex.ml
@@ -1,5 +1,5 @@
 open Multicore_bench
-open Picos_sync
+open Picos_std_sync
 
 module Ref = struct
   type 'a t = 'a ref
diff --git a/bench/bench_semaphore.ml b/bench/bench_semaphore.ml
index 48707e9..b1730a5 100644
--- a/bench/bench_semaphore.ml
+++ b/bench/bench_semaphore.ml
@@ -1,7 +1,7 @@
 open Multicore_bench
 open Picos
-open Picos_sync
-open Picos_structured
+open Picos_std_structured
+open Picos_std_sync
 
 let is_ocaml4 = String.starts_with ~prefix:"4." Sys.ocaml_version
 
diff --git a/bench/bench_stream.ml b/bench/bench_stream.ml
index c3f49d2..aba7547 100644
--- a/bench/bench_stream.ml
+++ b/bench/bench_stream.ml
@@ -1,5 +1,5 @@
 open Multicore_bench
-module Stream = Picos_sync.Stream
+module Stream = Picos_std_sync.Stream
 
 let run_one_domain ~budgetf ?(n_msgs = 50 * Util.iter_factor) () =
   let t = Stream.create ~padded:true () in
diff --git a/bench/bench_yield.ml b/bench/bench_yield.ml
index 34131bb..3ae3a68 100644
--- a/bench/bench_yield.ml
+++ b/bench/bench_yield.ml
@@ -1,5 +1,5 @@
 open Multicore_bench
-open Picos_structured
+open Picos_std_structured
 
 let factor =
   Util.iter_factor
diff --git a/bench/dune b/bench/dune
index d2da1b8..c324cd0 100644
--- a/bench/dune
+++ b/bench/dune
@@ -1,4 +1,5 @@
 (test
+ (package picos_meta)
  (name main)
  (action
   (progn
@@ -23,27 +24,27 @@
    (run %{test} -brief "Bounded_q with Picos_sync")
    (run %{test} -brief "Memory usage")))
  (libraries
+  backoff
+  multicore-bench
+  multicore-magic
   picos
   picos.domain
-  picos.finally
-  picos.htbl
-  picos.mpmcq
-  picos.mpscq
-  picos.select
-  picos.stdio
-  picos.structured
-  picos.sync
   picos.thread
+  picos_aux.htbl
+  picos_aux.mpmcq
+  picos_aux.mpscq
+  picos_std.finally
+  picos_std.structured
+  picos_std.sync
+  picos_stdio
+  picos_stdio.select
   (select
    scheduler.ml
    from
-   (picos.fifos -> scheduler.ocaml5.ml)
-   (picos.threaded -> scheduler.ocaml4.ml))
+   (picos_mux.fifo -> scheduler.ocaml5.ml)
+   (picos_mux.thread -> scheduler.ocaml4.ml))
   (select
    bench_fib.ml
    from
-   (picos.randos picos.multififos -> bench_fib.ocaml5.ml)
-   (-> bench_fib.ocaml4.ml))
-  multicore-bench
-  multicore-magic
-  backoff))
+   (picos_mux.random picos_mux.multififo -> bench_fib.ocaml5.ml)
+   (-> bench_fib.ocaml4.ml))))
diff --git a/bench/main.ml b/bench/main.ml
index 0612806..50a33de 100644
--- a/bench/main.ml
+++ b/bench/main.ml
@@ -1,4 +1,4 @@
-let () = Picos_select.configure ()
+let () = Picos_stdio_select.configure ()
 
 let benchmarks =
   [
diff --git a/bench/scheduler.ocaml4.ml b/bench/scheduler.ocaml4.ml
index f43325a..eaaa61a 100644
--- a/bench/scheduler.ocaml4.ml
+++ b/bench/scheduler.ocaml4.ml
@@ -1,1 +1,1 @@
-let run main = Picos_threaded.run main
+let run main = Picos_mux_thread.run main
diff --git a/bench/scheduler.ocaml5.ml b/bench/scheduler.ocaml5.ml
index f82ad8e..ffd930f 100644
--- a/bench/scheduler.ocaml5.ml
+++ b/bench/scheduler.ocaml5.ml
@@ -1,1 +1,1 @@
-let run main = Picos_fifos.run ~quota:100 main
+let run main = Picos_mux_fifo.run ~quota:100 main
diff --git a/dune b/dune
index 2a1f404..016bad0 100644
--- a/dune
+++ b/dune
@@ -1,3 +1,4 @@
 (mdx
- (libraries picos picos.exn_bt picos.finally picos.structured picos.sync)
+ (package picos_meta)
+ (libraries picos picos_std.finally picos_std.structured picos_std.sync)
  (files README.md))
diff --git a/dune-project b/dune-project
index 5f01218..99bc8fb 100644
--- a/dune-project
+++ b/dune-project
@@ -13,12 +13,26 @@
 (source
  (github ocaml-multicore/picos))
 
-(homepage "https://github.com/ocaml-multicore/picos")
+(homepage https://github.com/ocaml-multicore/picos)
 
-(license "ISC")
+(license ISC)
 
 (using mdx 0.4)
 
+(package
+ (name picos_aux)
+ (synopsis "Auxiliary libraries for Picos")
+ (depends
+  (ocaml
+   (>= 4.14.0))
+  ;;
+  (backoff
+   (>= 0.1.0))
+  (multicore-magic
+   (>= 2.3.0))
+  ;;
+  ))
+
 (package
  (name picos)
  (synopsis "Pico scheduler interface")
@@ -27,54 +41,153 @@
  (depends
   (backoff
    (>= 0.1.0))
-  ;; For OCaml 4 compatible defaults that are not used on OCaml 5 by default
   (thread-local-storage
    (>= 0.2))
+  ;;
+  ))
+
+(package
+ (name picos_std)
+ (synopsis "Sample libraries for Picos")
+ (depends
+  (picos
+   (= :version))
+  ;;
+  (backoff
+   (>= 0.1.0))
+  (multicore-magic
+   (>= 2.3.0))
+  ;;
+  ))
+
+(package
+ (name picos_stdio)
+ (synopsis "Asynchronous IO system for Picos")
+ (depends
+  (picos_aux
+   (= :version))
+  (picos_std
+   (= :version))
+  ;;
+  (backoff
+   (>= 0.1.0))
   (mtime
    (>= 2.0.0))
-  (psq
-   (>= 0.2.1))
-  ;; For mpsc queue
   (multicore-magic
    (>= 2.3.0))
-  ;; For picos.lwt
-  (lwt
-   (>= 5.7.0))
-  ;; For picos.stdio.cohttp
+  (psq
+   (>= 0.2.1))
+  ;;
+  ))
+
+(package
+ (name picos_stdio_cohttp)
+ (synopsis "Cohttp running on Picos stdio")
+ (depends
+  (picos_stdio
+   (= :version))
+  ;;
   (cohttp
    (>= 6.0.0~beta2))
-  (uri
-   (>= 4.4.0))
   (fmt
    (>= 0.9.0))
-  ;; Test dependencies
-  (multicore-bench
-   (and
-    (>= 0.1.4)
-    :with-test))
+  (uri
+   (>= 4.4.0))
+  ;;
+  ))
+
+(package
+ (name picos_mux)
+ (synopsis "Sample schedulers for Picos")
+ (depends
+  (picos_aux
+   (= :version))
+  (picos_std
+   (= :version))
+  ;;
+  (backoff
+   (>= 0.1.0))
+  (multicore-magic
+   (>= 2.3.0))
+  ;;
+  )
+ (depopts
+  (picos_stdio
+   (= :version))
+  ;;
+  ))
+
+(package
+ (name picos_lwt)
+ (allow_empty)
+ (synopsis "Lwt interface for Picos")
+ (depends
+  (picos_aux
+   (= :version))
+  (picos_std
+   (= :version))
+  ;;
+  (lwt
+   (>= 5.7.0))
+  ;;
+  ))
+
+(package
+ (name picos_meta)
+ (allow_empty)
+ (synopsis "Integration tests for Picos packages")
+ (depends
+  (picos
+   (= :version))
+  (picos_aux
+   (= :version))
+  (picos_std
+   (= :version))
+  (picos_stdio
+   (= :version))
+  (picos_stdio_cohttp
+   (= :version))
+  (picos_mux
+   (= :version))
+  (picos_lwt
+   (= :version))
+  ;;
+  (dscheck
+   (>= 0.4.0))
+  (lwt
+   (>= 5.7.0))
+  (qcheck-core
+   (>= 0.21.2))
+  (qcheck-stm
+   (>= 0.3))
+  ;;
   (alcotest
    (and
     (>= 1.7.0)
     :with-test))
-  (qcheck-core
+  (backoff
    (and
-    (>= 0.21.2)
+    (>= 0.1.0)
     :with-test))
-  (qcheck-stm
+  (cohttp
    (and
-    (>= 0.3)
+    (>= 6.0.0~beta2)
     :with-test))
-  (qcheck-multicoretests-util
+  (cohttp-lwt-unix
    (and
-    (>= 0.3)
+    (>= 6.0.0~beta2)
+    (<> :os "win32")
     :with-test))
-  (mdx
+  (conduit-lwt-unix
    (and
-    (>= 2.4.0)
+    (>= 6.2.2)
+    (<> :os "win32")
     :with-test))
-  (ocaml-version
+  (conf-npm
    (and
-    (>= 3.6.4)
+    (<> :arch "x86_32")
+    (<> :arch "riscv64")
+    (<> :os "win32")
     :with-test))
   (domain_shims
    (and
@@ -84,41 +197,46 @@
    (and
     (>= 5.4.0)
     :with-test))
-  (conf-npm
+  (mdx
    (and
-    (<> :arch "x86_32")
-    (<> :arch "riscv64")
-    (<> :os "win32")
+    (>= 2.4.0)
     :with-test))
-  (dscheck
+  (multicore-bench
    (and
-    (>= 0.4.0)
+    (>= 0.1.4)
     :with-test))
-  (multicore-magic-dscheck
+  (multicore-magic
    (and
     (>= 2.3.0)
     :with-test))
-  ;; Documentation dependencies
-  (sherlodoc
+  (multicore-magic-dscheck
    (and
-    (>= 0.2)
-    :with-doc))
-  (odoc
+    (>= 2.3.0)
+    :with-test))
+  (ocaml-version
    (and
-    (>= 2.4.1)
-    :with-doc))
-  (ocaml
-   (>= 4.14.0)))
- (depopts
+    (>= 3.6.4)
+    :with-test))
   (cohttp-lwt
    (and
-    (>= 5.3.0)
+    (>= 6.0.0~beta2)
     :with-test))
-  (cohttp-lwt-unix
+  (qcheck-multicoretests-util
    (and
-    (>= 5.3.0)
+    (>= 0.3)
     :with-test))
-  (conduit-lwt-unix
+  (uri
    (and
-    (>= 6.2.2)
-    :with-test))))
+    (>= 4.4.0)
+    :with-test))
+  ;;
+  (odoc
+   (and
+    (>= 2.4.1)
+    :with-doc))
+  (sherlodoc
+   (and
+    (>= 0.2)
+    :with-doc))
+  ;;
+  ))
diff --git a/lib/dune b/lib/dune
deleted file mode 100644
index a24f868..0000000
--- a/lib/dune
+++ /dev/null
@@ -1,1 +0,0 @@
-(documentation)
diff --git a/lib/picos_domain/dune b/lib/picos.domain/dune
similarity index 89%
rename from lib/picos_domain/dune
rename to lib/picos.domain/dune
index 16dc2f2..515ff2d 100644
--- a/lib/picos_domain/dune
+++ b/lib/picos.domain/dune
@@ -3,6 +3,7 @@
  (public_name picos.domain))
 
 (rule
+ (package picos)
  (enabled_if
   (< %{ocaml_version} 5.0.0))
  (action
@@ -10,6 +11,7 @@
    (copy picos_domain.ocaml4.ml picos_domain.ml))))
 
 (rule
+ (package picos)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (action
diff --git a/lib/picos_domain/picos_domain.mli b/lib/picos.domain/picos_domain.mli
similarity index 100%
rename from lib/picos_domain/picos_domain.mli
rename to lib/picos.domain/picos_domain.mli
diff --git a/lib/picos_domain/picos_domain.ocaml4.ml b/lib/picos.domain/picos_domain.ocaml4.ml
similarity index 100%
rename from lib/picos_domain/picos_domain.ocaml4.ml
rename to lib/picos.domain/picos_domain.ocaml4.ml
diff --git a/lib/picos_domain/picos_domain.ocaml5.ml b/lib/picos.domain/picos_domain.ocaml5.ml
similarity index 100%
rename from lib/picos_domain/picos_domain.ocaml5.ml
rename to lib/picos.domain/picos_domain.ocaml5.ml
diff --git a/lib/picos_thread/dune b/lib/picos.thread/dune
similarity index 93%
rename from lib/picos_thread/dune
rename to lib/picos.thread/dune
index c180b30..becdb3e 100644
--- a/lib/picos_thread/dune
+++ b/lib/picos.thread/dune
@@ -11,6 +11,7 @@
    (picos.domain !threads.posix -> picos_thread.none.ml))))
 
 (rule
+ (package picos)
  (enabled_if
   (< %{ocaml_version} 5.0.0))
  (action
@@ -18,6 +19,7 @@
    (copy thread.ocaml4.js thread.js))))
 
 (rule
+ (package picos)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (action
diff --git a/lib/picos_thread/picos_thread.mli b/lib/picos.thread/picos_thread.mli
similarity index 100%
rename from lib/picos_thread/picos_thread.mli
rename to lib/picos.thread/picos_thread.mli
diff --git a/lib/picos_thread/picos_thread.none.ml b/lib/picos.thread/picos_thread.none.ml
similarity index 100%
rename from lib/picos_thread/picos_thread.none.ml
rename to lib/picos.thread/picos_thread.none.ml
diff --git a/lib/picos_thread/picos_thread.posix.ml b/lib/picos.thread/picos_thread.posix.ml
similarity index 100%
rename from lib/picos_thread/picos_thread.posix.ml
rename to lib/picos.thread/picos_thread.posix.ml
diff --git a/lib/picos_thread/thread.ocaml4.js b/lib/picos.thread/thread.ocaml4.js
similarity index 100%
rename from lib/picos_thread/thread.ocaml4.js
rename to lib/picos.thread/thread.ocaml4.js
diff --git a/lib/picos_thread/thread.ocaml5.js b/lib/picos.thread/thread.ocaml5.js
similarity index 100%
rename from lib/picos_thread/thread.ocaml5.js
rename to lib/picos.thread/thread.ocaml5.js
diff --git a/lib/picos/bootstrap/dune b/lib/picos/bootstrap/dune
index b525fc3..446d0cf 100644
--- a/lib/picos/bootstrap/dune
+++ b/lib/picos/bootstrap/dune
@@ -1,6 +1,4 @@
 (library
  (name picos_bootstrap)
  (package picos)
- (libraries
-  (re_export picos.exn_bt)
-  backoff))
+ (libraries backoff))
diff --git a/lib/picos/bootstrap/picos_bootstrap.ml b/lib/picos/bootstrap/picos_bootstrap.ml
index 785c060..02778d4 100644
--- a/lib/picos/bootstrap/picos_bootstrap.ml
+++ b/lib/picos/bootstrap/picos_bootstrap.ml
@@ -1,7 +1,5 @@
 let[@inline never] impossible () = failwith "impossible"
 
-module Exn_bt = Picos_exn_bt
-
 module Trigger = struct
   let[@inline never] error_awaiting () = invalid_arg "already awaiting"
 
@@ -75,7 +73,8 @@ module Computation = struct
 
   and ('a, _) st =
     | Canceled : {
-        exn_bt : Exn_bt.t;
+        exn : exn;
+        bt : Printexc.raw_backtrace;
         mutable tx : [ `Stopped | `Running ] tx;
       }
         -> ('a, [> `Canceled ]) st
@@ -179,9 +178,9 @@ module Computation = struct
         value =
       try_complete tx computation Backoff.default (Returned { value; tx })
 
-    let[@inline] try_cancel (Running _ as tx : [ `Running ] tx) computation
-        exn_bt =
-      try_complete tx computation Backoff.default (Canceled { exn_bt; tx })
+    let[@inline] try_cancel (Running _ as tx : [ `Running ] tx) computation exn
+        bt =
+      try_complete tx computation Backoff.default (Canceled { exn; bt; tx })
   end
 
   let empty_fifo = S (Continue { triggers = []; balance_and_mode = fifo_bit })
@@ -202,7 +201,8 @@ module Computation = struct
 
   let canceled t =
     match Atomic.get t with
-    | S (Canceled { exn_bt; tx }) -> if tx == Stopped then Some exn_bt else None
+    | S (Canceled { exn; bt; tx }) ->
+        if tx == Stopped then Some (exn, bt) else None
     | S (Returned _) | S (Continue _) -> None
 
   (** [gc] is called when balance becomes negative by both [try_attach] and
@@ -294,40 +294,41 @@ module Computation = struct
   let try_return t value = try_terminate t (make_returned value) Backoff.default
   let try_finish t = try_terminate t returned_unit Backoff.default
 
-  let try_cancel t exn_bt =
-    try_terminate t (S (Canceled { exn_bt; tx = Stopped })) Backoff.default
+  let try_cancel t exn bt =
+    try_terminate t (S (Canceled { exn; bt; tx = Stopped })) Backoff.default
 
   let return t value = try_return t value |> ignore
   let finish t = try_finish t |> ignore
-  let cancel t exn_bt = try_cancel t exn_bt |> ignore
+  let cancel t exn bt = try_cancel t exn bt |> ignore
 
   let try_capture t fn x =
     match fn x with
     | y -> try_return t y
-    | exception exn -> try_cancel t (Exn_bt.get exn)
+    | exception exn -> try_cancel t exn (Printexc.get_raw_backtrace ())
 
   let capture t fn x = try_capture t fn x |> ignore
 
   let check t =
     match Atomic.get t with
-    | S (Canceled { exn_bt; tx; _ }) ->
-        if tx == Stopped then Exn_bt.raise exn_bt
+    | S (Canceled { exn; bt; tx; _ }) ->
+        if tx == Stopped then Printexc.raise_with_backtrace exn bt
     | S (Returned _) | S (Continue _) -> ()
 
   exception Running
 
   let peek_exn t =
     match Atomic.get t with
-    | S (Canceled { exn_bt; tx; _ }) ->
-        if tx == Stopped then Exn_bt.raise exn_bt else raise_notrace Running
+    | S (Canceled { exn; bt; tx; _ }) ->
+        if tx == Stopped then Printexc.raise_with_backtrace exn bt
+        else raise_notrace Running
     | S (Returned { value; tx; _ }) ->
         if tx == Stopped then value else raise_notrace Running
     | S (Continue _) -> raise_notrace Running
 
   let peek t =
     match Atomic.get t with
-    | S (Canceled { exn_bt; tx; _ }) ->
-        if tx == Stopped then Some (Error exn_bt) else None
+    | S (Canceled { exn; bt; tx; _ }) ->
+        if tx == Stopped then Some (Error (exn, bt)) else None
     | S (Returned { value; tx; _ }) ->
         if tx == Stopped then Some (Ok value) else None
     | S (Continue _) -> None
@@ -347,8 +348,9 @@ module Computation = struct
     match Atomic.get t with
     | S (Returned { value; tx; _ }) ->
         if tx == Stopped then value else get_or block (block t)
-    | S (Canceled { exn_bt; tx; _ }) ->
-        if tx == Stopped then Exn_bt.raise exn_bt else get_or block (block t)
+    | S (Canceled { exn; bt; tx; _ }) ->
+        if tx == Stopped then Printexc.raise_with_backtrace exn bt
+        else get_or block (block t)
     | S (Continue _) -> get_or block (block t)
 
   let attach_canceler ~from ~into =
@@ -506,7 +508,13 @@ module Handler = struct
     spawn : 'c -> Fiber.t -> (Fiber.t -> unit) -> unit;
     yield : 'c -> unit;
     cancel_after :
-      'a. 'c -> 'a Computation.t -> seconds:float -> Exn_bt.t -> unit;
-    await : 'c -> Trigger.t -> Exn_bt.t option;
+      'a.
+      'c ->
+      'a Computation.t ->
+      seconds:float ->
+      exn ->
+      Printexc.raw_backtrace ->
+      unit;
+    await : 'c -> Trigger.t -> (exn * Printexc.raw_backtrace) option;
   }
 end
diff --git a/lib/picos/dune b/lib/picos/dune
index cb2e4cb..c7cf7ae 100644
--- a/lib/picos/dune
+++ b/lib/picos/dune
@@ -2,7 +2,6 @@
  (name picos)
  (public_name picos)
  (libraries
-  (re_export picos.exn_bt)
   picos_bootstrap
   (select
    picos.ml
@@ -16,13 +15,19 @@
    (picos_ocaml5 -> intf.ocaml5.ml))))
 
 (mdx
+ (package picos_meta)
  (enabled_if
   (< %{ocaml_version} 5.0.0))
- (libraries picos picos.exn_bt picos.structured picos.threaded domain_shims)
+ (libraries picos picos_mux.thread picos_std.structured domain_shims)
  (files picos.mli))
 
 (mdx
+ (package picos_meta)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
- (libraries picos picos.exn_bt picos.structured picos.randos)
+ (libraries picos picos_mux.random picos_std.structured)
  (files picos.mli))
+
+(documentation
+ (package picos)
+ (mld_files index))
diff --git a/lib/index.mld b/lib/picos/index.mld
similarity index 76%
rename from lib/index.mld
rename to lib/picos/index.mld
index e66e6c2..40c2c90 100644
--- a/lib/index.mld
+++ b/lib/picos/index.mld
@@ -33,12 +33,11 @@ your work and making your work Picos compatible may offer benefits:
 - You may find it useful that the {{!Picos} core} of Picos provides parallelism
   safe building blocks for cancelation, which is a particularly tricky problem
   to get right.
-- You may find it useful that you don't have to reinvent many of the
-  {{!Picos_sync} basic communication and synchronization abstractions} such as
-  mutexes and condition variables, promises, concurrent bounded queues,
-  channels, and what not.
-- You may benefit from further non-trivial libraries, such as {{!Picos_stdio} IO
-  libraries}, that you don't have to reimplement.
+- You may find it useful that you don't have to reinvent many of the basic
+  communication and synchronization abstractions such as mutexes and condition
+  variables, promises, concurrent bounded queues, channels, and what not.
+- You may benefit from further non-trivial libraries, such as IO libraries, that
+  you don't have to reimplement.
 - Potential users of your work may be reassured and benefit from the ability to
   mix-and-match your work with other Picos compatible libraries and frameworks.
 
@@ -127,80 +126,6 @@ it is perhaps illuminating to explicitly mention some of those:
 Let's build an incredible ecosystem of interoperable concurrent programming
 libraries and frameworks!
 
-{1 Libraries}
-
-The Picos package is divided into multiple libraries.
-
-{2 Core}
-
-The only essential part of the package is the core interface.
-
-{!modules:
-  Picos
-}
-
-{^ Everything else is entirely opt-in and you are free to mix-and-match with any
-   other Picos compatible schedulers and libraries implemented in Picos or
-   develop your own.}
-
-{2 Sample schedulers}
-
-These are minimalistic, but fully-functioning, schedulers provided as samples.
-
-{!modules:
-  Picos_fifos
-  Picos_multififos
-  Picos_randos
-  Picos_lwt
-  Picos_lwt_unix
-  Picos_threaded
-}
-
-{^ You may find these useful for both understanding the core Picos interface and
-   for testing your own libraries implemented in Picos.}
-
-{2 Scheduler agnostic libraries}
-
-These are examples of libraries implemented in Picos.
-
-{!modules:
-  Picos_finally
-  Picos_structured
-  Picos_sync
-  Picos_stdio
-  Picos_stdio_cohttp
-  Picos_select
-}
-
-{^ These libraries are both meant to serve as examples of what can be done and
-   to also provide practical means for programming with fibers.  Hopefully there
-   will be many more libraries implemented in Picos like these providing
-   different approaches, patterns, and idioms for structuring concurrent
-   programs.}
-
-{^ The IO libraries in this package are built only on top of the standard
-   libraries distributed with OCaml and are hopefully useful for building simple
-   applications.  Bindings to asynchronous system IO libraries are outside the
-   scope of this package.}
-
-{2 Auxiliary libraries}
-
-These have no dependency to the core {!Picos} interface and are used in the
-implementation of the other libraries.
-
-{!modules:
-  Picos_domain
-  Picos_exn_bt
-  Picos_fd
-  Picos_htbl
-  Picos_mpmcq
-  Picos_mpscq
-  Picos_rc
-  Picos_thread
-}
-
-{^ Some of these libraries might be moved to other packages in the future.}
-
 {1 Conventions}
 
 Many operation in the Picos libraries use
diff --git a/lib/picos/intf.ocaml4.ml b/lib/picos/intf.ocaml4.ml
index e8139e4..cbe151f 100644
--- a/lib/picos/intf.ocaml4.ml
+++ b/lib/picos/intf.ocaml4.ml
@@ -1,15 +1,12 @@
 module type Trigger = sig
   type t
-  type exn_bt
 end
 
 module type Computation = sig
   type _ t
-  type exn_bt
 end
 
 module type Fiber = sig
   type t
   type _ computation
-  type exn_bt
 end
diff --git a/lib/picos/intf.ocaml5.ml b/lib/picos/intf.ocaml5.ml
index 0ad43d2..08fe0e9 100644
--- a/lib/picos/intf.ocaml5.ml
+++ b/lib/picos/intf.ocaml5.ml
@@ -1,6 +1,5 @@
 module type Trigger = sig
   type t
-  type exn_bt
 
   (** Schedulers must handle the {!Await} effect to implement the behavior of
       {!await}.
@@ -27,12 +26,13 @@ module type Trigger = sig
       as a response to {!Await}.
 
       The scheduler is free to choose which ready fiber to resume next. *)
-  type _ Effect.t += private Await : t -> exn_bt option Effect.t
+  type _ Effect.t +=
+    private
+    | Await : t -> (exn * Printexc.raw_backtrace) option Effect.t
 end
 
 module type Computation = sig
   type _ t
-  type exn_bt
 
   (** Schedulers must handle the {!Cancel_after} effect to implement the
       behavior of {!cancel_after}.
@@ -56,7 +56,8 @@ module type Computation = sig
     private
     | Cancel_after : {
         seconds : float;  (** Guaranteed to be non-negative. *)
-        exn_bt : exn_bt;
+        exn : exn;
+        bt : Printexc.raw_backtrace;
         computation : 'a t;
       }
         -> unit Effect.t
@@ -65,15 +66,17 @@ end
 module type Fiber = sig
   type t
   type _ computation
-  type exn_bt
 
-  val resume : t -> (exn_bt option, 'r) Effect.Deep.continuation -> 'r
+  val resume :
+    t ->
+    ((exn * Printexc.raw_backtrace) option, 'r) Effect.Deep.continuation ->
+    'r
   (** [resume fiber k] is equivalent to
       {{!Fiber.canceled} [Effect.Deep.continue k (Fiber.canceled t)]}. *)
 
   val resume_with :
     t ->
-    (exn_bt option, 'b) Effect.Shallow.continuation ->
+    ((exn * Printexc.raw_backtrace) option, 'b) Effect.Shallow.continuation ->
     ('b, 'r) Effect.Shallow.handler ->
     'r
   (** [resume_with fiber k h] is equivalent to
@@ -84,7 +87,8 @@ module type Fiber = sig
       {[
         match Fiber.canceled fiber with
         | None -> Effect.Deep.continue k v
-        | Some exn_bt -> Exn_bt.discontinue k exn_bt
+        | Some (exn, bt) ->
+          Effect.Deep.discontinue_with_backtrace k exn bt
       ]} *)
 
   val continue_with :
@@ -97,7 +101,8 @@ module type Fiber = sig
       {[
         match Fiber.canceled fiber with
         | None -> Effect.Shallow.continue_with k v h
-        | Some exn_bt -> Exn_bt.discontinue_with k exn_bt h
+        | Some (exn, bt) ->
+          Effect.Shallow.discontinue_with_backtrace k exn bt h
       ]} *)
 
   (** Schedulers must handle the {!Current} effect to implement the behavior of
diff --git a/lib/picos/ocaml4/dune b/lib/picos/ocaml4/dune
index cbd1982..bee32a4 100644
--- a/lib/picos/ocaml4/dune
+++ b/lib/picos/ocaml4/dune
@@ -4,7 +4,4 @@
  (wrapped false)
  (enabled_if
   (< %{ocaml_version} 5.0.0))
- (libraries
-  (re_export picos.exn_bt)
-  picos.thread
-  picos_bootstrap))
+ (libraries picos.thread picos_bootstrap))
diff --git a/lib/picos/ocaml4/picos_ocaml.ml b/lib/picos/ocaml4/picos_ocaml.ml
index 5c2e3eb..a796cee 100644
--- a/lib/picos/ocaml4/picos_ocaml.ml
+++ b/lib/picos/ocaml4/picos_ocaml.ml
@@ -10,7 +10,7 @@ module Handler = struct
     let current = error
     and spawn _ _ = error
     and yield = error
-    and cancel_after _ _ ~seconds:_ = error
+    and cancel_after _ _ ~seconds:_ _ = error
     and await _ = error in
     E { context = (); handler = { current; spawn; yield; cancel_after; await } }
 
@@ -55,8 +55,8 @@ module Fiber = struct
 end
 
 module Computation = struct
-  let cancel_after computation ~seconds exn_bt =
+  let cancel_after computation ~seconds exn bt =
     Computation.check_non_negative seconds;
     let (E r) = Handler.get () in
-    r.handler.cancel_after r.context computation ~seconds exn_bt
+    r.handler.cancel_after r.context computation ~seconds exn bt
 end
diff --git a/lib/picos/ocaml5/dune b/lib/picos/ocaml5/dune
index 3e21312..dbc0c33 100644
--- a/lib/picos/ocaml5/dune
+++ b/lib/picos/ocaml5/dune
@@ -4,6 +4,4 @@
  (wrapped false)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
- (libraries
-  (re_export picos.exn_bt)
-  picos_bootstrap))
+ (libraries picos_bootstrap))
diff --git a/lib/picos/ocaml5/picos_ocaml.ml b/lib/picos/ocaml5/picos_ocaml.ml
index f23fa03..d0555a3 100644
--- a/lib/picos/ocaml5/picos_ocaml.ml
+++ b/lib/picos/ocaml5/picos_ocaml.ml
@@ -1,7 +1,8 @@
 open Picos_bootstrap
 
 module Trigger = struct
-  type _ Effect.t += Await : Trigger.t -> Exn_bt.t option Effect.t
+  type _ Effect.t +=
+    | Await : Trigger.t -> (exn * Printexc.raw_backtrace) option Effect.t
 
   let await t = if Trigger.is_initial t then Effect.perform (Await t) else None
 end
@@ -13,12 +14,12 @@ module Fiber = struct
   let continue t k v =
     match Fiber.canceled t with
     | None -> Effect.Deep.continue k v
-    | Some exn_bt -> Exn_bt.discontinue k exn_bt
+    | Some (exn, bt) -> Effect.Deep.discontinue_with_backtrace k exn bt
 
   let continue_with t k v h =
     match Fiber.canceled t with
     | None -> Effect.Shallow.continue_with k v h
-    | Some exn_bt -> Exn_bt.discontinue_with k exn_bt h
+    | Some (exn, bt) -> Effect.Shallow.discontinue_with_backtrace k exn bt h
 
   type _ Effect.t += Current : Fiber.t Effect.t
 
@@ -38,14 +39,15 @@ module Computation = struct
   type _ Effect.t +=
     | Cancel_after : {
         seconds : float;
-        exn_bt : Exn_bt.t;
+        exn : exn;
+        bt : Printexc.raw_backtrace;
         computation : 'a Computation.t;
       }
         -> unit Effect.t
 
-  let cancel_after computation ~seconds exn_bt =
+  let cancel_after computation ~seconds exn bt =
     Computation.check_non_negative seconds;
-    Effect.perform (Cancel_after { seconds; exn_bt; computation })
+    Effect.perform (Cancel_after { seconds; exn; bt; computation })
 end
 
 module Handler = struct
@@ -82,7 +84,7 @@ module Handler = struct
           Some
             (fun k ->
               match
-                h.cancel_after c r.computation ~seconds:r.seconds r.exn_bt
+                h.cancel_after c r.computation ~seconds:r.seconds r.exn r.bt
               with
               | unit -> Effect.Deep.continue k unit
               | exception exn -> discontinue k exn)
diff --git a/lib/picos/picos.common.ml b/lib/picos/picos.common.ml
index 52e5392..836fe59 100644
--- a/lib/picos/picos.common.ml
+++ b/lib/picos/picos.common.ml
@@ -1,5 +1,3 @@
-module Exn_bt = Picos_exn_bt
-
 module Trigger = struct
   include Picos_bootstrap.Trigger
   include Picos_ocaml.Trigger
@@ -14,9 +12,9 @@ module Computation = struct
     if try_attach t trigger then begin
       match Trigger.await trigger with
       | None -> t
-      | Some exn_bt ->
+      | Some (exn, bt) ->
           detach t trigger;
-          Exn_bt.raise exn_bt
+          Printexc.raise_with_backtrace exn bt
     end
     else t
 
@@ -67,18 +65,18 @@ module Fiber = struct
 
   exception Done
 
-  let done_bt = Exn_bt.get_callstack 0 Done
+  let empty_bt = Printexc.get_callstack 0
 
   let sleep ~seconds =
     let sleep = Computation.create ~mode:`LIFO () in
-    Computation.cancel_after ~seconds sleep done_bt;
+    Computation.cancel_after ~seconds sleep Done empty_bt;
     let trigger = Trigger.create () in
     if Computation.try_attach sleep trigger then
       match Trigger.await trigger with
       | None -> ()
-      | Some exn_bt ->
+      | Some (exn, bt) ->
           Computation.finish sleep;
-          Exn_bt.raise exn_bt
+          Printexc.raise_with_backtrace exn bt
 end
 
 module Handler = struct
diff --git a/lib/picos/picos.mli b/lib/picos/picos.mli
index 1727d39..db3e49e 100644
--- a/lib/picos/picos.mli
+++ b/lib/picos/picos.mli
@@ -52,20 +52,21 @@
 
     - To be effective, cancelation should take effect as soon as possible.  In
       this case, cancelation should take effect even during the
-      {{!Picos_sync.Mutex.lock} [Mutex.lock]} inside
-      {{!Picos_sync.Mutex.protect} [Mutex.protect]} and the
-      {{!Picos_sync.Condition.wait} [Condition.wait]} operations when the fiber
-      might be in a suspended state awaiting for a signal to continue.
+      {{!Picos_std_sync.Mutex.lock} [Mutex.lock]} inside
+      {{!Picos_std_sync.Mutex.protect} [Mutex.protect]} and the
+      {{!Picos_std_sync.Condition.wait} [Condition.wait]} operations when the
+      fiber might be in a suspended state awaiting for a signal to continue.
     - To be safe, cancelation should not leave the program in an invalid state
       or cause the program to leak memory.  In this case, the ownership of the
       mutex must be transferred to the next fiber or be left unlocked and no
       references to unused objects must be left in the mutex or the condition
       variable.
 
-    Picos allows {{!Picos_sync.Mutex} [Mutex]} and {{!Picos_sync.Condition}
-    [Condition]} to be implemented such that cancelation may safely take effect
-    at or during calls to {{!Picos_sync.Mutex.lock} [Mutex.lock]} and
-    {{!Picos_sync.Condition.wait} [Condition.wait]}.
+    Picos allows {{!Picos_std_sync.Mutex} [Mutex]} and
+    {{!Picos_std_sync.Condition} [Condition]} to be implemented such that
+    cancelation may safely take effect at or during calls to
+    {{!Picos_std_sync.Mutex.lock} [Mutex.lock]} and
+    {{!Picos_std_sync.Condition.wait} [Condition.wait]}.
 
     {2 Cancelation in Picos}
 
@@ -113,32 +114,27 @@
       open Picos
     ]}
 
-    as well as the {!Picos_structured} library,
+    as well as the {!Picos_std_structured} library,
 
     {[
-      open Picos_structured
+      open Picos_std_structured
     ]}
 
     which we will be using for managing fibers in some of the examples, and
     define a simple scheduler on OCaml 4
 
     {@ocaml version<5.0.0[
-      let run main = Picos_threaded.run main
+      let run main = Picos_mux_thread.run main
     ]}
 
-    using {{!Picos_threaded} the basic thread based scheduler} and on OCaml 5
+    using {{!Picos_mux_thread} the basic thread based scheduler} and on OCaml 5
 
     {@ocaml version>=5.0.0[
-      let run main = Picos_randos.run_on ~n_domains:2 main
+      let run main = Picos_mux_random.run_on ~n_domains:2 main
     ]}
 
-    using {{!Picos_randos} the randomized effects based scheduler} that come
-    with Picos as samples.
-
-    {2 Auxiliary modules} *)
-
-module Exn_bt = Picos_exn_bt
-(** Exceptions with backtraces. *)
+    using {{!Picos_mux_random} the randomized effects based scheduler} that come
+    with Picos as samples. *)
 
 (** {2 Core modules} *)
 
@@ -165,9 +161,9 @@ module Trigger : sig
           | None ->
             (* We were resumed normally. *)
             ()
-          | Some exn_bt ->
+          | Some (exn, bt) ->
             (* We were canceled. *)
-            Exn_bt.raise exn_bt
+            Printexc.raise_with_backtrace exn bt
         end
       ]}
 
@@ -206,15 +202,15 @@ module Trigger : sig
       has not been signaled so when attaching a trigger to multiple computations
       there is no need to separately check with [is_signaled]. *)
 
-  val await : t -> Exn_bt.t option
+  val await : t -> (exn * Printexc.raw_backtrace) option
   (** [await trigger] waits for the trigger to be {!signal}ed.
 
       The return value is [None] in case the trigger has been signaled and the
       {{!Fiber} fiber} was resumed normally.  Otherwise the return value is
-      [Some exn_bt], which indicates that the fiber has been canceled and the
-      caller should {{!Exn_bt.raise} raise} the exception.  In either case
-      the caller is responsible for cleaning up.  Usually this means making sure
-      that no references to the trigger remain to avoid space leaks.
+      [Some (exn, bt)], which indicates that the fiber has been canceled and the
+      caller should raise the exception.  In either case the caller is
+      responsible for cleaning up.  Usually this means making sure that no
+      references to the trigger remain to avoid space leaks.
 
       ⚠️ As a rule of thumb, if you inserted the trigger to some data structure
       or {{!Computation.try_attach} attached} it to some computation, then you
@@ -325,7 +321,7 @@ module Trigger : sig
 
       @raise Invalid_argument if the trigger was in the awaiting state. *)
 
-  include Intf.Trigger with type t := t with type exn_bt := Exn_bt.t
+  include Intf.Trigger with type t := t
 
   (** {2 Design rationale}
 
@@ -389,7 +385,7 @@ module Trigger : sig
       atomically through code in this interface.  The key requirement left for
       the user is to make sure that the state of the shared data structure is
       updated correctly independently of what {!await} returns.  So, for
-      example, a mutex implementation must check, after getting [Some exn_bt],
+      example, a mutex implementation must check, after getting [Some (exn, bt)],
       what the state of the mutex is and how it should be updated. *)
 end
 
@@ -407,10 +403,10 @@ module Computation : sig
       {{!is_running} running to completed}.
 
       A hopefully enlightening analogy is that a computation is a kind of
-      single-shot atomic {{!Picos_sync.Event} event}.
+      single-shot atomic {{!Picos_std_event.Event} event}.
 
       Another hopefully helpful analogy is that a computation is basically like
-      a {{!Picos_structured.Promise} cancelable promise} and a basic
+      a {{!Picos_std_structured.Promise} cancelable promise} and a basic
       non-cancelable promise can be implemented trivially on top of a
       computation.
 
@@ -438,7 +434,7 @@ module Computation : sig
             Fiber.sleep ~seconds:1.0;
 
             Computation.cancel computation
-              (Exn_bt.get_callstack 0 Exit)
+              Exit (Printexc.get_callstack 0)
           in
 
           Flock.fork begin fun () ->
@@ -482,7 +478,7 @@ module Computation : sig
   type !'a t
   (** Represents a cancelable computation.  A computation is either {i running}
       or has been {i completed} either with a return value or with canceling
-      {{!Exn_bt} exception with a backtrace}.
+      exception with a backtrace.
 
       ℹ️ Once a computation becomes completed it no longer changes state.
 
@@ -578,11 +574,12 @@ module Computation : sig
         that transaction was aborted and it is as if none of the completions
         succesfully added to the transaction have taken place. *)
 
-    val try_cancel : t -> 'a computation -> Exn_bt.t -> bool
-    (** [try_cancel tx computation exn_bt] adds the completion of the
-        [computation] as having canceled with the given [exn_bt] to the
-        transaction.  Returns [true] in case the computation had not yet been
-        completed and the transaction was still alive.  Otherwise returns
+    val try_cancel :
+      t -> 'a computation -> exn -> Printexc.raw_backtrace -> bool
+    (** [try_cancel tx computation exn bt] adds the completion of the
+        computation as having canceled with the given exception and backtrace to
+        the transaction.  Returns [true] in case the computation had not yet
+        been completed and the transaction was still alive.  Otherwise returns
         [false] which means that transaction was aborted and it is as if none of
         the completions succesfully added to the transaction have taken
         place. *)
@@ -601,20 +598,21 @@ module Computation : sig
   (** An existential wrapper for computations. *)
   type packed = Packed : 'a t -> packed
 
-  val try_cancel : 'a t -> Exn_bt.t -> bool
-  (** [try_cancel computation exn_bt] attempts to mark the computation as
+  val try_cancel : 'a t -> exn -> Printexc.raw_backtrace -> bool
+  (** [try_cancel computation exn bt] attempts to mark the computation as
       canceled with the specified exception and backtrace and returns [true] on
       success.  Otherwise returns [false], which means that the computation had
       already been completed before. *)
 
-  val cancel : 'a t -> Exn_bt.t -> unit
-  (** [cancel computation exn_bt] is equivalent to
-      {{!try_cancel} [try_cancel computation exn_bt |> ignore]}. *)
+  val cancel : 'a t -> exn -> Printexc.raw_backtrace -> unit
+  (** [cancel computation exn bt] is equivalent to
+      {{!try_cancel} [try_cancel computation exn bt |> ignore]}. *)
 
   (** {2 Interface for timeouts} *)
 
-  val cancel_after : 'a t -> seconds:float -> Exn_bt.t -> unit
-  (** [cancel_after ~seconds computation exn_bt] arranges to {!cancel} the
+  val cancel_after :
+    'a t -> seconds:float -> exn -> Printexc.raw_backtrace -> unit
+  (** [cancel_after ~seconds computation exn bt] arranges to {!cancel} the
       computation after the specified time with the specified exception and
       backtrace.  Completion of the computation before the specified time
       effectively cancels the timeout.
@@ -639,16 +637,16 @@ module Computation : sig
   (** [is_canceled computation] determines whether the computation is in the
       canceled state. *)
 
-  val canceled : 'a t -> Exn_bt.t option
+  val canceled : 'a t -> (exn * Printexc.raw_backtrace) option
   (** [canceled computation] returns the exception that the computation has been
       canceled with or returns [None] in case the computation has not been
       canceled. *)
 
   val check : 'a t -> unit
   (** [check computation] is equivalent to
-      {{!canceled} [Option.iter Exn_bt.raise (canceled computation)]}. *)
+      {{!canceled} [Option.iter (fun (exn, bt) -> Printexc.raise_with_backtrace exn bt) (canceled computation)]}. *)
 
-  val peek : 'a t -> ('a, Exn_bt.t) result option
+  val peek : 'a t -> ('a, exn * Printexc.raw_backtrace) result option
   (** [peek computation] returns the result of the computation or [None] in case
       the computation has not completed. *)
 
@@ -724,7 +722,7 @@ module Computation : sig
 
   (** {2 Interface for schedulers} *)
 
-  include Intf.Computation with type 'a t := 'a t with type exn_bt := Exn_bt.t
+  include Intf.Computation with type 'a t := 'a t
 
   val with_action :
     ?mode:[ `FIFO | `LIFO ] ->
@@ -758,16 +756,16 @@ module Computation : sig
       case getting a notification is no longer necessary.  This allows the
       status change to be propagated omnidirectionally and is what makes
       computations able to support a variety of purposes such as the
-      implementation of {{!Picos_structured} structured concurrency}.
+      implementation of {{!Picos_std_structured} structured concurrency}.
 
       The computation concept can be seen as a kind of single-shot atomic
-      {{!Picos_sync.Event} event} that is a generalization of both a cancelation
-      context or token and of a {{!Picos_structured.Promise} promise}.  Unlike a
-      typical promise mechanism, a computation can be canceled.  Unlike a
-      typical cancelation mechanism, a computation can and should also be
-      completed in case it is not canceled.  This promotes proper scoping of
-      computations and resource cleanup at completion, which is how the design
-      evolved from a more traditional cancelation context design.
+      {{!Picos_std_event.Event} event} that is a generalization of both a
+      cancelation Context or token and of a {{!Picos_std_structured.Promise}
+      promise}.  Unlike a typical promise mechanism, a computation can be
+      canceled.  Unlike a typical cancelation mechanism, a computation can and
+      should also be completed in case it is not canceled.  This promotes proper
+      scoping of computations and resource cleanup at completion, which is how
+      the design evolved from a more traditional cancelation context design.
 
       Every fiber is {{!Fiber.get_computation} associated with a computation}.
       Being able to return a value through the computation means that no
@@ -909,7 +907,7 @@ module Fiber : sig
 
       ⚠️ It is only safe to call [is_canceled] from the fiber itself. *)
 
-  val canceled : t -> Exn_bt.t option
+  val canceled : t -> (exn * Printexc.raw_backtrace) option
   (** [canceled fiber] is equivalent to:
       {@ocaml skip[
         if Fiber.has_forbidden fiber then
@@ -1142,10 +1140,7 @@ module Fiber : sig
       ⚠️ The trigger must be in the signaled state! *)
 
   include
-    Intf.Fiber
-      with type t := t
-      with type 'a computation := 'a Computation.t
-      with type exn_bt := Exn_bt.t
+    Intf.Fiber with type t := t with type 'a computation := 'a Computation.t
 
   (** {2 Design rationale}
 
@@ -1183,9 +1178,15 @@ module Handler : sig
         (** See {!Picos.Fiber.spawn}. *)
     yield : 'c -> unit;  (** See {!Picos.Fiber.yield}. *)
     cancel_after :
-      'a. 'c -> 'a Computation.t -> seconds:float -> Exn_bt.t -> unit;
+      'a.
+      'c ->
+      'a Computation.t ->
+      seconds:float ->
+      exn ->
+      Printexc.raw_backtrace ->
+      unit;
         (** See {!Picos.Computation.cancel_after}. *)
-    await : 'c -> Trigger.t -> Exn_bt.t option;
+    await : 'c -> Trigger.t -> (exn * Printexc.raw_backtrace) option;
         (** See {!Picos.Trigger.await}. *)
   }
   (** A record of implementations of the primitive effects based operations of
diff --git a/lib/picos_aux.htbl/dune b/lib/picos_aux.htbl/dune
new file mode 100644
index 0000000..da84bc7
--- /dev/null
+++ b/lib/picos_aux.htbl/dune
@@ -0,0 +1,11 @@
+(library
+ (name picos_aux_htbl)
+ (public_name picos_aux.htbl)
+ (libraries backoff multicore-magic))
+
+(mdx
+ (package picos_meta)
+ (enabled_if
+  (>= %{ocaml_version} 5.1.0))
+ (libraries picos_aux.htbl)
+ (files picos_aux_htbl.mli))
diff --git a/lib/picos_htbl/picos_htbl.ml b/lib/picos_aux.htbl/picos_aux_htbl.ml
similarity index 100%
rename from lib/picos_htbl/picos_htbl.ml
rename to lib/picos_aux.htbl/picos_aux_htbl.ml
diff --git a/lib/picos_htbl/picos_htbl.mli b/lib/picos_aux.htbl/picos_aux_htbl.mli
similarity index 91%
rename from lib/picos_htbl/picos_htbl.mli
rename to lib/picos_aux.htbl/picos_aux_htbl.mli
index d6da567..eaed5de 100644
--- a/lib/picos_htbl/picos_htbl.mli
+++ b/lib/picos_aux.htbl/picos_aux_htbl.mli
@@ -100,23 +100,23 @@ val remove_all : ('k, 'v) t -> ('k * 'v) Seq.t
 
     An example top-level session:
     {[
-      # let t : (int, string) Picos_htbl.t =
-          Picos_htbl.create
+      # let t : (int, string) Picos_aux_htbl.t =
+          Picos_aux_htbl.create
             ~hashed_type:(module Int) ()
-      val t : (int, string) Picos_htbl.t = <abstr>
+      val t : (int, string) Picos_aux_htbl.t = <abstr>
 
-      # Picos_htbl.try_add t 42 "The answer"
+      # Picos_aux_htbl.try_add t 42 "The answer"
       - : bool = true
 
-      # Picos_htbl.try_add t 101 "Basics"
+      # Picos_aux_htbl.try_add t 101 "Basics"
       - : bool = true
 
-      # Picos_htbl.find_exn t 42
+      # Picos_aux_htbl.find_exn t 42
       - : string = "The answer"
 
-      # Picos_htbl.try_add t 101 "The basics"
+      # Picos_aux_htbl.try_add t 101 "The basics"
       - : bool = false
 
-      # Picos_htbl.remove_all t |> List.of_seq
+      # Picos_aux_htbl.remove_all t |> List.of_seq
       - : (int * string) list = [(101, "Basics"); (42, "The answer")]
     ]} *)
diff --git a/lib/picos_aux.mpmcq/dune b/lib/picos_aux.mpmcq/dune
new file mode 100644
index 0000000..fe0406d
--- /dev/null
+++ b/lib/picos_aux.mpmcq/dune
@@ -0,0 +1,9 @@
+(library
+ (name picos_aux_mpmcq)
+ (public_name picos_aux.mpmcq)
+ (libraries backoff multicore-magic))
+
+(mdx
+ (package picos_meta)
+ (libraries picos_aux.mpmcq)
+ (files picos_aux_mpmcq.mli))
diff --git a/lib/picos_mpmcq/picos_mpmcq.ml b/lib/picos_aux.mpmcq/picos_aux_mpmcq.ml
similarity index 100%
rename from lib/picos_mpmcq/picos_mpmcq.ml
rename to lib/picos_aux.mpmcq/picos_aux_mpmcq.ml
diff --git a/lib/picos_mpmcq/picos_mpmcq.mli b/lib/picos_aux.mpmcq/picos_aux_mpmcq.mli
similarity index 79%
rename from lib/picos_mpmcq/picos_mpmcq.mli
rename to lib/picos_aux.mpmcq/picos_aux_mpmcq.mli
index 8cd5451..f8f280a 100644
--- a/lib/picos_mpmcq/picos_mpmcq.mli
+++ b/lib/picos_aux.mpmcq/picos_aux_mpmcq.mli
@@ -42,31 +42,31 @@ val is_empty : 'a t -> bool
 
     An example top-level session:
     {[
-      # let q : int Picos_mpmcq.t =
-          Picos_mpmcq.create ()
-      val q : int Picos_mpmcq.t = <abstr>
+      # let q : int Picos_aux_mpmcq.t =
+          Picos_aux_mpmcq.create ()
+      val q : int Picos_aux_mpmcq.t = <abstr>
 
-      # Picos_mpmcq.push q 42
+      # Picos_aux_mpmcq.push q 42
       - : unit = ()
 
-      # Picos_mpmcq.push_head q 76
+      # Picos_aux_mpmcq.push_head q 76
       - : unit = ()
 
-      # Picos_mpmcq.length q
+      # Picos_aux_mpmcq.length q
       - : int = 2
 
-      # Picos_mpmcq.push q 101
+      # Picos_aux_mpmcq.push q 101
       - : unit = ()
 
-      # Picos_mpmcq.pop_exn q
+      # Picos_aux_mpmcq.pop_exn q
       - : int = 76
 
-      # Picos_mpmcq.pop_exn q
+      # Picos_aux_mpmcq.pop_exn q
       - : int = 42
 
-      # Picos_mpmcq.pop_exn q
+      # Picos_aux_mpmcq.pop_exn q
       - : int = 101
 
-      # Picos_mpmcq.pop_exn q
-      Exception: Picos_mpmcq.Empty.
+      # Picos_aux_mpmcq.pop_exn q
+      Exception: Picos_aux_mpmcq.Empty.
     ]} *)
diff --git a/lib/picos_aux.mpscq/dune b/lib/picos_aux.mpscq/dune
new file mode 100644
index 0000000..94d1558
--- /dev/null
+++ b/lib/picos_aux.mpscq/dune
@@ -0,0 +1,9 @@
+(library
+ (name picos_aux_mpscq)
+ (public_name picos_aux.mpscq)
+ (libraries backoff multicore-magic))
+
+(mdx
+ (package picos_meta)
+ (libraries picos_aux.mpscq)
+ (files picos_aux_mpscq.mli))
diff --git a/lib/picos_mpscq/picos_mpscq.ml b/lib/picos_aux.mpscq/picos_aux_mpscq.ml
similarity index 100%
rename from lib/picos_mpscq/picos_mpscq.ml
rename to lib/picos_aux.mpscq/picos_aux_mpscq.ml
diff --git a/lib/picos_mpscq/picos_mpscq.mli b/lib/picos_aux.mpscq/picos_aux_mpscq.mli
similarity index 83%
rename from lib/picos_mpscq/picos_mpscq.mli
rename to lib/picos_aux.mpscq/picos_aux_mpscq.mli
index 28b74dd..1365fd5 100644
--- a/lib/picos_mpscq/picos_mpscq.mli
+++ b/lib/picos_aux.mpscq/picos_aux_mpscq.mli
@@ -44,22 +44,22 @@ val pop_all : 'a t -> 'a Seq.t
 
     An example top-level session:
     {[
-      # let q : int Picos_mpscq.t =
-          Picos_mpscq.create ()
-      val q : int Picos_mpscq.t = <abstr>
+      # let q : int Picos_aux_mpscq.t =
+          Picos_aux_mpscq.create ()
+      val q : int Picos_aux_mpscq.t = <abstr>
 
-      # Picos_mpscq.push q 42
+      # Picos_aux_mpscq.push q 42
       - : unit = ()
 
-      # Picos_mpscq.push_head q 76
+      # Picos_aux_mpscq.push_head q 76
       - : unit = ()
 
-      # Picos_mpscq.push q 101
+      # Picos_aux_mpscq.push q 101
       - : unit = ()
 
-      # Picos_mpscq.pop_exn q
+      # Picos_aux_mpscq.pop_exn q
       - : int = 76
 
-      # Picos_mpscq.pop_all q |> List.of_seq
+      # Picos_aux_mpscq.pop_all q |> List.of_seq
       - : int list = [42; 101]
     ]} *)
diff --git a/lib/picos_aux.rc/dune b/lib/picos_aux.rc/dune
new file mode 100644
index 0000000..ce3ff23
--- /dev/null
+++ b/lib/picos_aux.rc/dune
@@ -0,0 +1,4 @@
+(library
+ (name picos_aux_rc)
+ (public_name picos_aux.rc)
+ (libraries backoff picos_aux.htbl))
diff --git a/lib/picos_rc/intf.ml b/lib/picos_aux.rc/intf.ml
similarity index 100%
rename from lib/picos_rc/intf.ml
rename to lib/picos_aux.rc/intf.ml
diff --git a/lib/picos_rc/picos_rc.ml b/lib/picos_aux.rc/picos_aux_rc.ml
similarity index 92%
rename from lib/picos_rc/picos_rc.ml
rename to lib/picos_aux.rc/picos_aux_rc.ml
index 5e17fec..a059b57 100644
--- a/lib/picos_rc/picos_rc.ml
+++ b/lib/picos_aux.rc/picos_aux_rc.ml
@@ -14,13 +14,15 @@ let count_1 = 1 lsl count_shift
 let dispose_bit = 0b01
 let closed_bit = 0b10
 
+module Htbl = Picos_aux_htbl
+
 module Make (Resource : Resource) () : S with module Resource = Resource =
 struct
   module Resource = Resource
 
   type entry = { count_and_bits : int; bt : Printexc.raw_backtrace }
 
-  let ht = Picos_htbl.create ~hashed_type:(module Resource) ()
+  let ht = Htbl.create ~hashed_type:(module Resource) ()
 
   type t = Resource.t
 
@@ -29,7 +31,7 @@ struct
       match bt with Some bt -> bt | None -> Printexc.get_callstack 15
     in
     if
-      Picos_htbl.try_add ht t
+      Htbl.try_add ht t
         (Atomic.make { count_and_bits = count_1 lor Bool.to_int dispose; bt })
     then t
     else begin
@@ -53,7 +55,7 @@ struct
         incr t entry (Backoff.once backoff)
 
   let incr t =
-    match Picos_htbl.find_exn ht t with
+    match Htbl.find_exn ht t with
     | exception Not_found -> disposed ()
     | entry -> incr t entry Backoff.default
 
@@ -66,14 +68,14 @@ struct
       if not (Atomic.compare_and_set entry before after) then
         decr closed_bit t entry (Backoff.once backoff)
       else if count_and_bits < count_1 then begin
-        Picos_htbl.try_remove ht t |> ignore;
+        Htbl.try_remove ht t |> ignore;
         (* We must dispose the resource as the last step, because the value
            might be reused after it has been disposed. *)
         if after.count_and_bits land dispose_bit <> 0 then Resource.dispose t
       end
 
   let decr ?close t =
-    match Picos_htbl.find_exn ht t with
+    match Htbl.find_exn ht t with
     | exception Not_found -> disposed ()
     | entry ->
         decr
@@ -89,7 +91,7 @@ struct
   }
 
   let infos () =
-    Picos_htbl.to_seq ht
+    Htbl.to_seq ht
     |> Seq.map @@ fun (resource, entry) ->
        let { count_and_bits; bt } = Atomic.get entry in
        let count = count_and_bits lsr count_shift in
diff --git a/lib/picos_rc/picos_rc.mli b/lib/picos_aux.rc/picos_aux_rc.mli
similarity index 67%
rename from lib/picos_rc/picos_rc.mli
rename to lib/picos_aux.rc/picos_aux_rc.mli
index 7cae396..0d6986a 100644
--- a/lib/picos_rc/picos_rc.mli
+++ b/lib/picos_aux.rc/picos_aux_rc.mli
@@ -1,5 +1,4 @@
-(** External reference counting tables for {{!Resource} disposable
-    resources}. *)
+(** External reference counting tables for disposable resources. *)
 
 include module type of Intf
 
diff --git a/lib/picos_aux/dune b/lib/picos_aux/dune
new file mode 100644
index 0000000..e79d24b
--- /dev/null
+++ b/lib/picos_aux/dune
@@ -0,0 +1,3 @@
+(documentation
+ (package picos_aux)
+ (mld_files index))
diff --git a/lib/picos_aux/index.mld b/lib/picos_aux/index.mld
new file mode 100644
index 0000000..69dbf5d
--- /dev/null
+++ b/lib/picos_aux/index.mld
@@ -0,0 +1,13 @@
+{0 Auxiliary libraries for Picos}
+
+This package contains auxiliary libraries used in the implementation of other
+Picos libraries.
+
+{!modules:
+  Picos_aux_htbl
+  Picos_aux_mpmcq
+  Picos_aux_mpscq
+  Picos_aux_rc
+}
+
+{^ Some of these libraries might be moved to other packages in the future.}
diff --git a/lib/picos_exn_bt/common.ml b/lib/picos_exn_bt/common.ml
deleted file mode 100644
index 8131df5..0000000
--- a/lib/picos_exn_bt/common.ml
+++ /dev/null
@@ -1,21 +0,0 @@
-module type Intf = sig
-  type t = { exn : exn; bt : Printexc.raw_backtrace }
-  (** An exception and a backtrace. *)
-
-  val get : exn -> t
-  (** [get exn] is equivalent to
-      [{ exn; bt = Printexc.get_raw_backtrace () }]. *)
-
-  val get_callstack : int -> exn -> t
-  (** [get_callstack n exn] is equivalent to
-      [{ exn; bt = Printexc.get_callstack n }].
-
-      Note that [Printexc.get_callstack 0] effectively returns a constant value
-      and this function is optimized to take that into account. *)
-
-  val raise : t -> 'a
-  (** [raise exn_bt] is equivalent to
-      [Printexc.raise_with_backtrace exn_bt.exn exn_bt.bt]. *)
-end
-
-type t = { exn : exn; bt : Printexc.raw_backtrace }
diff --git a/lib/picos_exn_bt/dune b/lib/picos_exn_bt/dune
deleted file mode 100644
index cb2ea36..0000000
--- a/lib/picos_exn_bt/dune
+++ /dev/null
@@ -1,19 +0,0 @@
-(library
- (name picos_exn_bt)
- (public_name picos.exn_bt))
-
-(rule
- (enabled_if
-  (< %{ocaml_version} 5.0.0))
- (action
-  (progn
-   (copy picos_exn_bt.ocaml4.ml picos_exn_bt.ml)
-   (copy picos_exn_bt.ocaml4.mli picos_exn_bt.mli))))
-
-(rule
- (enabled_if
-  (>= %{ocaml_version} 5.0.0))
- (action
-  (progn
-   (copy picos_exn_bt.ocaml5.ml picos_exn_bt.ml)
-   (copy picos_exn_bt.ocaml5.mli picos_exn_bt.mli))))
diff --git a/lib/picos_exn_bt/picos_exn_bt.ocaml4.ml b/lib/picos_exn_bt/picos_exn_bt.ocaml4.ml
deleted file mode 100644
index b8ae64f..0000000
--- a/lib/picos_exn_bt/picos_exn_bt.ocaml4.ml
+++ /dev/null
@@ -1,15 +0,0 @@
-include Common
-
-(* We always inline [get] and [get_callstack] to get correct callstacks. *)
-
-let[@inline always] get exn =
-  let bt = Printexc.get_raw_backtrace () in
-  { exn; bt }
-
-let empty_backtrace = Printexc.get_callstack 0
-
-let[@inline always] get_callstack n exn =
-  let bt = if n <= 0 then empty_backtrace else Printexc.get_callstack n in
-  { exn; bt }
-
-let raise t = Printexc.raise_with_backtrace t.exn t.bt
diff --git a/lib/picos_exn_bt/picos_exn_bt.ocaml4.mli b/lib/picos_exn_bt/picos_exn_bt.ocaml4.mli
deleted file mode 100644
index 44d1d87..0000000
--- a/lib/picos_exn_bt/picos_exn_bt.ocaml4.mli
+++ /dev/null
@@ -1,3 +0,0 @@
-(** Wrapper for exceptions with backtraces. *)
-
-include Common.Intf
diff --git a/lib/picos_exn_bt/picos_exn_bt.ocaml5.ml b/lib/picos_exn_bt/picos_exn_bt.ocaml5.ml
deleted file mode 100644
index 5565a31..0000000
--- a/lib/picos_exn_bt/picos_exn_bt.ocaml5.ml
+++ /dev/null
@@ -1,22 +0,0 @@
-include Common
-
-(* We always inline [get] and [get_callstack] to get correct callstacks. *)
-
-let[@inline always] get exn =
-  let bt = Printexc.get_raw_backtrace () in
-  { exn; bt }
-
-let empty_backtrace = Printexc.get_callstack 0
-
-let[@inline always] get_callstack n exn =
-  let bt = if n <= 0 then empty_backtrace else Printexc.get_callstack n in
-  { exn; bt }
-
-let raise t = Printexc.raise_with_backtrace t.exn t.bt
-
-(* *)
-
-let discontinue k t = Effect.Deep.discontinue_with_backtrace k t.exn t.bt
-
-let discontinue_with k t handler =
-  Effect.Shallow.discontinue_with_backtrace k t.exn t.bt handler
diff --git a/lib/picos_exn_bt/picos_exn_bt.ocaml5.mli b/lib/picos_exn_bt/picos_exn_bt.ocaml5.mli
deleted file mode 100644
index 71ced62..0000000
--- a/lib/picos_exn_bt/picos_exn_bt.ocaml5.mli
+++ /dev/null
@@ -1,15 +0,0 @@
-(** Wrapper for exceptions with backtraces. *)
-
-include Common.Intf
-
-val discontinue : ('a, 'b) Effect.Deep.continuation -> t -> 'b
-(** [discontinue k exn_bt] is equivalent to
-    [Effect.Deep.discontinue_with_backtrace k exn_bt.exn exn_bt.bt]. *)
-
-val discontinue_with :
-  ('a, 'b) Effect.Shallow.continuation ->
-  t ->
-  ('b, 'c) Effect.Shallow.handler ->
-  'c
-(** [discontinue_with k exn_bt h] is equivalent to
-    [Effect.Shallow.discontinue_with_backtrace k exn_bt.exn exn_bt.bt h]. *)
diff --git a/lib/picos_fd/dune b/lib/picos_fd/dune
deleted file mode 100644
index 7423bbc..0000000
--- a/lib/picos_fd/dune
+++ /dev/null
@@ -1,6 +0,0 @@
-(library
- (name picos_fd)
- (public_name picos.fd)
- (libraries
-  (re_export unix)
-  picos.rc))
diff --git a/lib/picos_fifos/select.some.ml b/lib/picos_fifos/select.some.ml
deleted file mode 100644
index aa39c6b..0000000
--- a/lib/picos_fifos/select.some.ml
+++ /dev/null
@@ -1,2 +0,0 @@
-let cancel_after = Picos_select.cancel_after
-let check_configured = Picos_select.check_configured
diff --git a/lib/picos_finally/dune b/lib/picos_finally/dune
deleted file mode 100644
index 6a09f1c..0000000
--- a/lib/picos_finally/dune
+++ /dev/null
@@ -1,14 +0,0 @@
-(library
- (name picos_finally)
- (public_name picos.finally)
- (libraries picos))
-
-(mdx
- (libraries
-  picos.exn_bt
-  picos.finally
-  picos.stdio
-  picos.structured
-  picos.sync
-  unix)
- (files picos_finally.mli))
diff --git a/lib/picos_htbl/dune b/lib/picos_htbl/dune
deleted file mode 100644
index f6d019e..0000000
--- a/lib/picos_htbl/dune
+++ /dev/null
@@ -1,10 +0,0 @@
-(library
- (name picos_htbl)
- (public_name picos.htbl)
- (libraries backoff multicore-magic))
-
-(mdx
- (enabled_if
-  (>= %{ocaml_version} 5.1.0))
- (libraries picos.htbl)
- (files picos_htbl.mli))
diff --git a/lib/picos_lwt_unix/dune b/lib/picos_lwt.unix/dune
similarity index 62%
rename from lib/picos_lwt_unix/dune
rename to lib/picos_lwt.unix/dune
index 9658854..8996dc9 100644
--- a/lib/picos_lwt_unix/dune
+++ b/lib/picos_lwt.unix/dune
@@ -1,11 +1,11 @@
 (library
  (name picos_lwt_unix)
- (public_name picos.lwt_unix)
+ (public_name picos_lwt.unix)
  (optional)
  (libraries
-  (re_export picos.lwt)
   (re_export lwt)
   (re_export lwt.unix)
-  picos.mpscq
+  (re_export picos_lwt)
   picos.thread
+  picos_aux.mpscq
   unix))
diff --git a/lib/picos_lwt_unix/picos_lwt_unix.ml b/lib/picos_lwt.unix/picos_lwt_unix.ml
similarity index 92%
rename from lib/picos_lwt_unix/picos_lwt_unix.ml
rename to lib/picos_lwt.unix/picos_lwt_unix.ml
index 9573a9a..bb13f71 100644
--- a/lib/picos_lwt_unix/picos_lwt_unix.ml
+++ b/lib/picos_lwt.unix/picos_lwt_unix.ml
@@ -3,7 +3,9 @@ open Picos
 let[@inline never] not_main_thread () =
   invalid_arg "not called from the main thread"
 
-let ready = Picos_mpscq.create ~padded:true ()
+module Mpscq = Picos_aux_mpscq
+
+let ready = Mpscq.create ~padded:true ()
 
 type notification = { mutable ref_count : int; mutable id : int }
 
@@ -13,11 +15,11 @@ let state = Atomic.make `Not_running
 let notify_callback () =
   Atomic.set state `Running;
   let rec loop () =
-    match Picos_mpscq.pop_exn ready with
+    match Mpscq.pop_exn ready with
     | resolver ->
         Lwt.wakeup resolver ();
         loop ()
-    | exception Picos_mpscq.Empty -> begin
+    | exception Mpscq.Empty -> begin
         match Atomic.get state with
         | `Not_running | `Notified ->
             Atomic.set state `Running;
@@ -49,7 +51,7 @@ module System = struct
   let signal (_, resolver) =
     if Picos_thread.is_main_thread () then Lwt.wakeup resolver ()
     else begin
-      Picos_mpscq.push ready resolver;
+      Mpscq.push ready resolver;
       notify ()
     end
 
diff --git a/lib/picos_lwt_unix/picos_lwt_unix.mli b/lib/picos_lwt.unix/picos_lwt_unix.mli
similarity index 100%
rename from lib/picos_lwt_unix/picos_lwt_unix.mli
rename to lib/picos_lwt.unix/picos_lwt_unix.mli
diff --git a/lib/picos_lwt/dune b/lib/picos_lwt/dune
index 0a3cfdf..36d8754 100644
--- a/lib/picos_lwt/dune
+++ b/lib/picos_lwt/dune
@@ -1,9 +1,13 @@
 (library
  (name picos_lwt)
- (public_name picos.lwt)
+ (public_name picos_lwt)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (libraries
   (re_export picos)
   (re_export lwt)
   picos.thread))
+
+(documentation
+ (package picos_lwt)
+ (mld_files index))
diff --git a/lib/picos_lwt/index.mld b/lib/picos_lwt/index.mld
new file mode 100644
index 0000000..8ebe86b
--- /dev/null
+++ b/lib/picos_lwt/index.mld
@@ -0,0 +1,9 @@
+{0 Lwt interface for Picos}
+
+This package provides an alternative direct style interface to programming with
+Lwt via {!Picos}.
+
+{!modules:
+  Picos_lwt
+  Picos_lwt_unix
+}
diff --git a/lib/picos_lwt/picos_lwt.ml b/lib/picos_lwt/picos_lwt.ml
index 917f6ac..8583e66 100644
--- a/lib/picos_lwt/picos_lwt.ml
+++ b/lib/picos_lwt/picos_lwt.ml
@@ -5,6 +5,8 @@ include Intf
 let[@inline never] not_main_thread () =
   invalid_arg "not called from the main thread"
 
+let empty_bt = Printexc.get_callstack 0
+
 let await promise =
   match Lwt.state promise with
   | Sleep ->
@@ -17,7 +19,7 @@ let await promise =
             Computation.return computation value;
             Lwt.return_unit)
           (fun exn ->
-            Computation.cancel computation (Exn_bt.get_callstack 0 exn);
+            Computation.cancel computation exn empty_bt;
             Lwt.return_unit)
       in
       Lwt.async (fun () -> promise);
@@ -25,9 +27,9 @@ let await promise =
       if Computation.try_attach computation trigger then begin
         match Trigger.await trigger with
         | None -> Computation.await computation
-        | Some exn_bt ->
+        | Some (exn, bt) ->
             Lwt.cancel promise;
-            Exn_bt.raise exn_bt
+            Printexc.raise_with_backtrace exn bt
       end
       else Computation.await computation
   | Return value -> value
@@ -38,7 +40,7 @@ let[@alert "-handler"] rec go :
     Fiber.t ->
     (module System) ->
     (a, r) Effect.Shallow.continuation ->
-    (a, Exn_bt.t) Result.t ->
+    (a, exn * Printexc.raw_backtrace) Result.t ->
     r Lwt.t =
  fun fiber ((module System) as system) k v ->
   let effc (type a) :
@@ -68,7 +70,7 @@ let[@alert "-handler"] rec go :
                   Lwt.try_bind
                     (fun () -> System.sleep r.seconds)
                     (fun () ->
-                      Computation.cancel r.computation r.exn_bt;
+                      Computation.cancel r.computation r.exn r.bt;
                       Lwt.return_unit)
                     (function
                       | Lwt.Canceled -> Lwt.return_unit | exn -> Lwt.reraise exn)
@@ -98,7 +100,8 @@ let[@alert "-handler"] rec go :
   let handler = Effect.Shallow.{ retc = Lwt.return; exnc = Lwt.fail; effc } in
   match v with
   | Ok v -> Effect.Shallow.continue_with k v handler
-  | Error exn_bt -> Exn_bt.discontinue_with k exn_bt handler
+  | Error (exn, bt) ->
+      Effect.Shallow.discontinue_with_backtrace k exn bt handler
 
 let run_fiber system fiber main =
   if not (Picos_thread.is_main_thread ()) then not_main_thread ();
diff --git a/lib/picos_meta/dune b/lib/picos_meta/dune
new file mode 100644
index 0000000..c318e1a
--- /dev/null
+++ b/lib/picos_meta/dune
@@ -0,0 +1,3 @@
+(documentation
+ (package picos_meta)
+ (mld_files index))
diff --git a/lib/picos_meta/index.mld b/lib/picos_meta/index.mld
new file mode 100644
index 0000000..2603149
--- /dev/null
+++ b/lib/picos_meta/index.mld
@@ -0,0 +1,5 @@
+{0 Integration tests for Picos packages}
+
+This package contains integration (and other kinds of) tests for the core
+{!Picos} interface and other Picos libraries.  A separate package is used to
+allow the dependencies of and between different Picos packages to be simplified.
diff --git a/lib/picos_mpmcq/dune b/lib/picos_mpmcq/dune
deleted file mode 100644
index 42be4d5..0000000
--- a/lib/picos_mpmcq/dune
+++ /dev/null
@@ -1,8 +0,0 @@
-(library
- (name picos_mpmcq)
- (public_name picos.mpmcq)
- (libraries backoff multicore-magic))
-
-(mdx
- (libraries picos.mpmcq)
- (files picos_mpmcq.mli))
diff --git a/lib/picos_mpscq/dune b/lib/picos_mpscq/dune
deleted file mode 100644
index 2be09f6..0000000
--- a/lib/picos_mpscq/dune
+++ /dev/null
@@ -1,8 +0,0 @@
-(library
- (name picos_mpscq)
- (public_name picos.mpscq)
- (libraries backoff multicore-magic))
-
-(mdx
- (libraries picos.mpscq)
- (files picos_mpscq.mli))
diff --git a/lib/picos_multififos/select.some.ml b/lib/picos_multififos/select.some.ml
deleted file mode 100644
index aa39c6b..0000000
--- a/lib/picos_multififos/select.some.ml
+++ /dev/null
@@ -1,2 +0,0 @@
-let cancel_after = Picos_select.cancel_after
-let check_configured = Picos_select.check_configured
diff --git a/lib/picos_fifos/dune b/lib/picos_mux.fifo/dune
similarity index 57%
rename from lib/picos_fifos/dune
rename to lib/picos_mux.fifo/dune
index b03fff0..79d3502 100644
--- a/lib/picos_fifos/dune
+++ b/lib/picos_mux.fifo/dune
@@ -1,6 +1,6 @@
 (library
- (name picos_fifos)
- (public_name picos.fifos)
+ (name picos_mux_fifo)
+ (public_name picos_mux.fifo)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (libraries
@@ -8,7 +8,7 @@
   (select
    select.ml
    from
-   (picos.select -> select.some.ml)
+   (picos_stdio.select -> select.some.ml)
    (-> select.none.ml))
   multicore-magic
-  picos.mpscq))
+  picos_aux.mpscq))
diff --git a/lib/picos_fifos/picos_fifos.ml b/lib/picos_mux.fifo/picos_mux_fifo.ml
similarity index 89%
rename from lib/picos_fifos/picos_fifos.ml
rename to lib/picos_mux.fifo/picos_mux_fifo.ml
index 1267334..aae2416 100644
--- a/lib/picos_fifos/picos_fifos.ml
+++ b/lib/picos_mux.fifo/picos_mux_fifo.ml
@@ -7,11 +7,15 @@ let[@inline never] quota_non_positive () = invalid_arg "quota must be positive"
 type ready =
   | Spawn of Fiber.t * (Fiber.t -> unit)
   | Continue of Fiber.t * (unit, unit) Effect.Deep.continuation
-  | Resume of Fiber.t * (Exn_bt.t option, unit) Effect.Deep.continuation
+  | Resume of
+      Fiber.t
+      * ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation
   | Return of Fiber.Maybe.t * (unit, unit) Effect.Deep.continuation
 
+module Mpscq = Picos_aux_mpscq
+
 type t = {
-  ready : ready Picos_mpscq.t;
+  ready : ready Mpscq.t;
   needs_wakeup : bool Atomic.t;
   num_alive_fibers : int Atomic.t;
   mutex : Mutex.t;
@@ -19,7 +23,7 @@ type t = {
   resume :
     Trigger.t ->
     Fiber.t ->
-    (Exn_bt.t option, unit) Effect.Deep.continuation ->
+    ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation ->
     unit;
   current : ((Fiber.t, unit) Effect.Deep.continuation -> unit) option;
   yield : ((unit, unit) Effect.Deep.continuation -> unit) option;
@@ -32,7 +36,7 @@ type t = {
 }
 
 let rec next t =
-  match Picos_mpscq.pop_exn t.ready with
+  match Mpscq.pop_exn t.ready with
   | Spawn (fiber, main) ->
       t.fiber <- Fiber.Maybe.of_fiber fiber;
       t.remaining_quota <- t.quota;
@@ -49,7 +53,7 @@ let rec next t =
       t.fiber <- Fiber.Maybe.of_fiber fiber;
       t.remaining_quota <- t.quota;
       Fiber.resume fiber k
-  | exception Picos_mpscq.Empty ->
+  | exception Mpscq.Empty ->
       t.fiber <- Fiber.Maybe.nothing;
       if Atomic.get t.num_alive_fibers <> 0 then begin
         if Atomic.get t.needs_wakeup then begin
@@ -79,7 +83,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
         quota
   in
   Select.check_configured ();
-  let ready = Picos_mpscq.create ~padded:true ()
+  let ready = Mpscq.create ~padded:true ()
   and needs_wakeup = Atomic.make false |> Multicore_magic.copy_as_padded
   and num_alive_fibers = Atomic.make 1 |> Multicore_magic.copy_as_padded
   and mutex = Mutex.create ()
@@ -113,7 +117,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
     Some
       (fun k ->
         let fiber = Fiber.Maybe.to_fiber t.fiber in
-        Picos_mpscq.push t.ready (Continue (fiber, k));
+        Mpscq.push t.ready (Continue (fiber, k));
         next t)
   and return =
     Some
@@ -124,7 +128,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
           Effect.Deep.continue k ()
         end
         else begin
-          Picos_mpscq.push t.ready (Return (t.fiber, k));
+          Mpscq.push t.ready (Return (t.fiber, k));
           next t
         end)
   and discontinue =
@@ -147,7 +151,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
         if Fiber.is_canceled fiber then t.discontinue
         else begin
           Atomic.incr t.num_alive_fibers;
-          Picos_mpscq.push t.ready (Spawn (r.fiber, r.main));
+          Mpscq.push t.ready (Spawn (r.fiber, r.main));
           t.return
         end
     | Fiber.Yield -> t.yield
@@ -158,12 +162,12 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
         if Fiber.is_canceled fiber then t.discontinue
         else
           match
-            Select.cancel_after r.computation ~seconds:r.seconds r.exn_bt
+            Select.cancel_after r.computation ~seconds:r.seconds r.exn r.bt
           with
           | () -> t.return
           | exception exn ->
-              let exn_bt = Exn_bt.get exn in
-              Some (fun k -> Exn_bt.discontinue k exn_bt)
+              let bt = Printexc.get_raw_backtrace () in
+              Some (fun k -> Effect.Deep.discontinue_with_backtrace k exn bt)
       end
     | Trigger.Await trigger ->
         Some
@@ -177,7 +181,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
                 Fiber.resume fiber k
               end
               else begin
-                Picos_mpscq.push t.ready (Resume (fiber, k));
+                Mpscq.push t.ready (Resume (fiber, k));
                 next t
               end)
     | _ -> None
@@ -188,11 +192,11 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
     let resume = Resume (fiber, k) in
     if Fiber.unsuspend fiber trigger then
       (* The fiber has not been canceled, so we queue the fiber normally. *)
-      Picos_mpscq.push t.ready resume
+      Mpscq.push t.ready resume
     else
       (* The fiber has been canceled, so we give priority to it in this
          scheduler. *)
-      Picos_mpscq.push_head t.ready resume;
+      Mpscq.push_head t.ready resume;
     (* As the trigger might have been signaled from another domain or systhread
        outside of the scheduler, we check whether the scheduler needs to be
        woken up and take care of it if necessary. *)
@@ -213,7 +217,7 @@ let run_fiber ?quota ?fatal_exn_handler:(exnc : _ = raise) fiber main =
       Condition.broadcast t.condition
     end
   in
-  Picos_mpscq.push t.ready (Spawn (fiber, main));
+  Mpscq.push t.ready (Spawn (fiber, main));
   next t
 
 let run ?quota ?fatal_exn_handler ?(forbid = false) main =
diff --git a/lib/picos_fifos/picos_fifos.mli b/lib/picos_mux.fifo/picos_mux_fifo.mli
similarity index 80%
rename from lib/picos_fifos/picos_fifos.mli
rename to lib/picos_mux.fifo/picos_mux_fifo.mli
index cc32f88..3ed4705 100644
--- a/lib/picos_fifos/picos_fifos.mli
+++ b/lib/picos_mux.fifo/picos_mux_fifo.mli
@@ -8,14 +8,15 @@
     🐌 Due to FIFO scheduling this scheduler performs poorly on highly parallel
     workloads.
 
-    ℹ️ See {!Picos_multififos} for a multi-threaded variation of this scheduler.
+    ℹ️ See {!Picos_std_multififos} for a multi-threaded variation of this
+    scheduler.
 
     ℹ️ This scheduler implementation is mostly meant as an example and for use in
     testing libraries implemented in {!Picos}.
 
-    ⚠️ This scheduler uses {!Picos_select} internally.  If running multiple
-    threads that each run this scheduler, {!Picos_select.configure} must be
-    called by the main thread before creating other threads. *)
+    ⚠️ This scheduler uses {!Picos_stdio_select} internally.  If running multiple
+    threads that each run this scheduler, {!Picos_stdio_select.configure} must
+    be called by the main thread before creating other threads. *)
 
 open Picos
 
diff --git a/lib/picos_fifos/select.none.ml b/lib/picos_mux.fifo/select.none.ml
similarity index 100%
rename from lib/picos_fifos/select.none.ml
rename to lib/picos_mux.fifo/select.none.ml
diff --git a/lib/picos_mux.fifo/select.some.ml b/lib/picos_mux.fifo/select.some.ml
new file mode 100644
index 0000000..e4981d3
--- /dev/null
+++ b/lib/picos_mux.fifo/select.some.ml
@@ -0,0 +1,2 @@
+let cancel_after = Picos_stdio_select.cancel_after
+let check_configured = Picos_stdio_select.check_configured
diff --git a/lib/picos_multififos/dune b/lib/picos_mux.multififo/dune
similarity index 58%
rename from lib/picos_multififos/dune
rename to lib/picos_mux.multififo/dune
index 3cdf8d4..cdcafef 100644
--- a/lib/picos_multififos/dune
+++ b/lib/picos_mux.multififo/dune
@@ -1,6 +1,6 @@
 (library
- (name picos_multififos)
- (public_name picos.multififos)
+ (name picos_mux_multififo)
+ (public_name picos_mux.multififo)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (libraries
@@ -8,8 +8,8 @@
   (select
    select.ml
    from
-   (picos.select -> select.some.ml)
+   (picos_stdio.select -> select.some.ml)
    (-> select.none.ml))
   multicore-magic
   picos.thread
-  picos.mpmcq))
+  picos_aux.mpmcq))
diff --git a/lib/picos_multififos/picos_multififos.ml b/lib/picos_mux.multififo/picos_mux_multififo.ml
similarity index 87%
rename from lib/picos_multififos/picos_multififos.ml
rename to lib/picos_mux.multififo/picos_mux_multififo.ml
index fcab1cf..0ce9fc3 100644
--- a/lib/picos_multififos/picos_multififos.ml
+++ b/lib/picos_mux.multififo/picos_mux_multififo.ml
@@ -4,10 +4,14 @@ let[@inline never] quota_non_positive () = invalid_arg "quota must be positive"
 let[@inline never] already_running () = invalid_arg "already running"
 let[@inline never] not_worker () = invalid_arg "not a worker thread"
 
+module Mpmcq = Picos_aux_mpmcq
+
 type ready =
   | Spawn of Fiber.t * (Fiber.t -> unit)
   | Continue of Fiber.t * (unit, unit) Effect.Deep.continuation
-  | Resume of Fiber.t * (Exn_bt.t option, unit) Effect.Deep.continuation
+  | Resume of
+      Fiber.t
+      * ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation
   | Return of Fiber.Maybe.t * (unit, unit) Effect.Deep.continuation
 
 type t = {
@@ -26,11 +30,11 @@ type t = {
 and _ tdt =
   | Nothing : [> `Nothing ] tdt
   | Per_thread : {
-      ready : ready Picos_mpmcq.t;
+      ready : ready Mpmcq.t;
       resume :
         Trigger.t ->
         Fiber.t ->
-        (Exn_bt.t option, unit) Effect.Deep.continuation ->
+        ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation ->
         unit;
       return : ((unit, unit) Effect.Deep.continuation -> unit) option;
       discontinue : ((unit, unit) Effect.Deep.continuation -> unit) option;
@@ -80,7 +84,7 @@ let rec any_fibers_ready t i =
   0 <= i
   &&
   let (Per_thread p) = get_thread t i in
-  Picos_mpmcq.length p.ready != 0 || any_fibers_ready t (i - 1)
+  Mpmcq.length p.ready != 0 || any_fibers_ready t (i - 1)
 
 let any_fibers_ready t = any_fibers_ready t (t.threads_num - 1)
 
@@ -105,16 +109,16 @@ let exec ready (Per_thread p : per_thread) t =
       Fiber.resume fiber k
 
 let rec next (Per_thread p as pt : per_thread) =
-  match Picos_mpmcq.pop_exn p.ready with
+  match Mpmcq.pop_exn p.ready with
   | ready ->
       let t = p.context in
-      if t.num_waiters_non_zero && Picos_mpmcq.length p.ready != 0 then begin
+      if t.num_waiters_non_zero && Mpmcq.length p.ready != 0 then begin
         Mutex.lock t.mutex;
         Mutex.unlock t.mutex;
         Condition.signal t.condition
       end;
       exec ready pt t
-  | exception Picos_mpmcq.Empty ->
+  | exception Mpmcq.Empty ->
       p.fiber <- Fiber.Maybe.nothing;
       let t = p.context in
       try_steal pt t (next_index t p.index)
@@ -122,9 +126,9 @@ let rec next (Per_thread p as pt : per_thread) =
 and try_steal (Per_thread p as pt : per_thread) t i =
   if p.index <> i then begin
     let (Per_thread other_p) = get_thread t i in
-    match Picos_mpmcq.pop_exn other_p.ready with
+    match Mpmcq.pop_exn other_p.ready with
     | ready -> exec ready pt t
-    | exception Picos_mpmcq.Empty -> try_steal pt t (next_index t i)
+    | exception Mpmcq.Empty -> try_steal pt t (next_index t i)
   end
   else wait pt t
 
@@ -169,7 +173,7 @@ let default_fatal_exn_handler exn =
   exit 2
 
 let per_thread context =
-  let ready = Picos_mpmcq.create ~padded:true () in
+  let ready = Mpmcq.create ~padded:true () in
   let rec pt : per_thread =
     Per_thread
       {
@@ -190,14 +194,12 @@ let per_thread context =
     match Picos_thread.TLS.get_exn per_thread_key with
     | Per_thread p_current when p_original.context == p_current.context ->
         (* We are running on a thread of this scheduler - no wakup needed. *)
-        if Fiber.unsuspend fiber trigger then
-          Picos_mpmcq.push p_current.ready resume
-        else Picos_mpmcq.push_head p_current.ready resume
+        if Fiber.unsuspend fiber trigger then Mpmcq.push p_current.ready resume
+        else Mpmcq.push_head p_current.ready resume
     | _ | (exception Picos_thread.TLS.Not_set) ->
         (* We are running on a foreign thread - wake up may be needed. *)
-        if Fiber.unsuspend fiber trigger then
-          Picos_mpmcq.push p_original.ready resume
-        else Picos_mpmcq.push_head p_original.ready resume;
+        if Fiber.unsuspend fiber trigger then Mpmcq.push p_original.ready resume
+        else Mpmcq.push_head p_original.ready resume;
         let t = p_original.context in
         let non_zero =
           match Mutex.lock t.mutex with
@@ -218,7 +220,7 @@ let per_thread context =
           Effect.Deep.continue k ()
         end
         else begin
-          Picos_mpmcq.push p.ready (Return (p.fiber, k));
+          Mpmcq.push p.ready (Return (p.fiber, k));
           next pt
         end)
   and discontinue =
@@ -280,7 +282,7 @@ let yield =
     (fun k ->
       let (Per_thread p as pt) = get_per_thread () in
       let fiber = Fiber.Maybe.to_fiber p.fiber in
-      Picos_mpmcq.push p.ready (Continue (fiber, k));
+      Mpmcq.push p.ready (Continue (fiber, k));
       next pt)
 
 let[@alert "-handler"] effc :
@@ -295,7 +297,7 @@ let[@alert "-handler"] effc :
         p.num_started <- p.num_started + 1;
         (* The queue [push] includes a full fence, which means the increment
            of [num_started] will happen before increment of [num_stopped]. *)
-        Picos_mpmcq.push p.ready (Spawn (r.fiber, r.main));
+        Mpmcq.push p.ready (Spawn (r.fiber, r.main));
         let t = p.context in
         if t.num_waiters_non_zero then begin
           Mutex.lock t.mutex;
@@ -310,11 +312,13 @@ let[@alert "-handler"] effc :
       let fiber = Fiber.Maybe.to_fiber p.fiber in
       if Fiber.is_canceled fiber then p.discontinue
       else
-        match Select.cancel_after r.computation ~seconds:r.seconds r.exn_bt with
+        match
+          Select.cancel_after r.computation ~seconds:r.seconds r.exn r.bt
+        with
         | () -> p.return
         | exception exn ->
-            let exn_bt = Exn_bt.get exn in
-            Some (fun k -> Exn_bt.discontinue k exn_bt)
+            let bt = Printexc.get_raw_backtrace () in
+            Some (fun k -> Effect.Deep.discontinue_with_backtrace k exn bt)
     end
   | Trigger.Await trigger ->
       Some
@@ -329,7 +333,7 @@ let[@alert "-handler"] effc :
               Fiber.resume fiber k
             end
             else begin
-              Picos_mpmcq.push p.ready (Resume (fiber, k));
+              Mpmcq.push p.ready (Resume (fiber, k));
               next pt
             end)
   | _ -> None
@@ -392,7 +396,7 @@ let run_fiber ?context:t_opt fiber main =
     t.run <- true;
     Mutex.unlock t.mutex
   end;
-  Picos_mpmcq.push p.ready (Spawn (fiber, main));
+  Mpmcq.push p.ready (Spawn (fiber, main));
   next pt
 
 let run ?context ?(forbid = false) main =
@@ -414,11 +418,19 @@ let rec run_fiber_on n fiber main runner_main context =
     in
     match run_fiber_on (n - 1) fiber main runner_main context with
     | result ->
-        Option.iter Exn_bt.raise (Domain.join runner);
+        begin
+          match Domain.join runner with
+          | None -> ()
+          | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
+        end;
         result
     | exception exn ->
         let bt = Printexc.get_raw_backtrace () in
-        Option.iter Exn_bt.raise (Domain.join runner);
+        begin
+          match Domain.join runner with
+          | None -> ()
+          | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
+        end;
         Printexc.raise_with_backtrace exn bt
 
 let run_fiber_on ?quota ?fatal_exn_handler ~n_domains fiber main =
@@ -432,7 +444,9 @@ let run_fiber_on ?quota ?fatal_exn_handler ~n_domains fiber main =
         Printexc.record_backtrace bt_status;
         match runner_on_this_thread context with
         | () -> None
-        | exception exn -> Some (Exn_bt.get exn)
+        | exception exn ->
+            let bt = Printexc.get_raw_backtrace () in
+            Some (exn, bt)
   in
   run_fiber_on n_domains fiber main runner_main context
 
diff --git a/lib/picos_multififos/picos_multififos.mli b/lib/picos_mux.multififo/picos_mux_multififo.mli
similarity index 89%
rename from lib/picos_multififos/picos_multififos.mli
rename to lib/picos_mux.multififo/picos_mux_multififo.mli
index 298c67f..3b5ef6a 100644
--- a/lib/picos_multififos/picos_multififos.mli
+++ b/lib/picos_mux.multififo/picos_mux_multififo.mli
@@ -10,14 +10,14 @@
     🐌 Due to mostly FIFO scheduling this scheduler performs poorly on highly
     parallel workloads.
 
-    ℹ️ See {!Picos_fifos} for a single-threaded variation of this scheduler.
+    ℹ️ See {!Picos_std_fifos} for a single-threaded variation of this scheduler.
 
     ℹ️ This scheduler implementation is mostly meant as an example and for use in
     testing libraries implemented in {!Picos}.
 
-    ⚠️ This scheduler uses {!Picos_select} internally.  If running multiple
-    threads that each run this scheduler, {!Picos_select.configure} must be
-    called by the main thread before creating other threads. *)
+    ⚠️ This scheduler uses {!Picos_stdio_select} internally.  If running multiple
+    threads that each run this scheduler, {!Picos_stdio_select.configure} must
+    be called by the main thread before creating other threads. *)
 
 open Picos
 
diff --git a/lib/picos_multififos/select.none.ml b/lib/picos_mux.multififo/select.none.ml
similarity index 100%
rename from lib/picos_multififos/select.none.ml
rename to lib/picos_mux.multififo/select.none.ml
diff --git a/lib/picos_mux.multififo/select.some.ml b/lib/picos_mux.multififo/select.some.ml
new file mode 100644
index 0000000..e4981d3
--- /dev/null
+++ b/lib/picos_mux.multififo/select.some.ml
@@ -0,0 +1,2 @@
+let cancel_after = Picos_stdio_select.cancel_after
+let check_configured = Picos_stdio_select.check_configured
diff --git a/lib/picos_randos/dune b/lib/picos_mux.random/dune
similarity index 59%
rename from lib/picos_randos/dune
rename to lib/picos_mux.random/dune
index e3a74e9..dac7f58 100644
--- a/lib/picos_randos/dune
+++ b/lib/picos_mux.random/dune
@@ -1,6 +1,6 @@
 (library
- (name picos_randos)
- (public_name picos.randos)
+ (name picos_mux_random)
+ (public_name picos_mux.random)
  (enabled_if
   (>= %{ocaml_version} 5.0.0))
  (libraries
@@ -8,8 +8,8 @@
   (select
    select.ml
    from
-   (picos.select -> select.some.ml)
+   (picos_stdio.select -> select.some.ml)
    (-> select.none.ml))
   multicore-magic
   picos.thread
-  picos.htbl))
+  picos_aux.htbl))
diff --git a/lib/picos_randos/picos_randos.ml b/lib/picos_mux.random/picos_mux_random.ml
similarity index 87%
rename from lib/picos_randos/picos_randos.ml
rename to lib/picos_mux.random/picos_mux_random.ml
index 7e1d74a..41fd9dc 100644
--- a/lib/picos_randos/picos_randos.ml
+++ b/lib/picos_mux.random/picos_mux_random.ml
@@ -1,7 +1,8 @@
 open Picos
+module Htbl = Picos_aux_htbl
 
 module Collection = struct
-  type 'a t = (int, 'a) Picos_htbl.t
+  type 'a t = (int, 'a) Htbl.t
 
   module Key = struct
     type t = int
@@ -10,28 +11,32 @@ module Collection = struct
     let hash = Fun.id
   end
 
-  let create () = Picos_htbl.create ~hashed_type:(module Key) ()
+  let create () = Htbl.create ~hashed_type:(module Key) ()
 
   let rec push t value =
     let key = Random.bits () in
-    if not (Picos_htbl.try_add t key value) then push t value
+    if not (Htbl.try_add t key value) then push t value
 
   let rec pop_exn t =
-    let key = Picos_htbl.find_random_exn t in
-    try Picos_htbl.remove_exn t key with Not_found -> pop_exn t
+    let key = Htbl.find_random_exn t in
+    try Htbl.remove_exn t key with Not_found -> pop_exn t
 
   let is_empty t =
-    match Picos_htbl.find_random_exn t with
-    | _ -> false
-    | exception Not_found -> true
+    match Htbl.find_random_exn t with _ -> false | exception Not_found -> true
 end
 
 type ready =
   | Spawn of Fiber.t * (Fiber.t -> unit)
   | Current of Fiber.t * (Fiber.t, unit) Effect.Deep.continuation
   | Continue of Fiber.t * (unit, unit) Effect.Deep.continuation
-  | Resume of Fiber.t * (Exn_bt.t option, unit) Effect.Deep.continuation
-  | Raise of Fiber.t * (unit, unit) Effect.Deep.continuation * Exn_bt.t
+  | Resume of
+      Fiber.t
+      * ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation
+  | Raise of
+      Fiber.t
+      * (unit, unit) Effect.Deep.continuation
+      * exn
+      * Printexc.raw_backtrace
   | Return of Fiber.t * (unit, unit) Effect.Deep.continuation
 
 type t = {
@@ -41,7 +46,7 @@ type t = {
   resume :
     Trigger.t ->
     Fiber.t ->
-    (Exn_bt.t option, unit) Effect.Deep.continuation ->
+    ((exn * Printexc.raw_backtrace) option, unit) Effect.Deep.continuation ->
     unit;
   num_waiters : int ref;
   condition : Condition.t;
@@ -68,9 +73,9 @@ let exec p t = function
   | Spawn (fiber, main) ->
       p := Fiber.Maybe.of_fiber fiber;
       Effect.Deep.match_with main fiber t.handler
-  | Raise (fiber, k, exn_bt) ->
+  | Raise (fiber, k, exn, bt) ->
       p := Fiber.Maybe.of_fiber fiber;
-      Exn_bt.discontinue k exn_bt
+      Effect.Deep.discontinue_with_backtrace k exn bt
   | Return (fiber, k) ->
       p := Fiber.Maybe.of_fiber fiber;
       Effect.Deep.continue k ()
@@ -220,14 +225,14 @@ let context ?fatal_exn_handler () =
         if Fiber.is_canceled fiber then t.yield
         else
           match
-            Select.cancel_after r.computation ~seconds:r.seconds r.exn_bt
+            Select.cancel_after r.computation ~seconds:r.seconds r.exn r.bt
           with
           | () -> t.return
           | exception exn ->
-              let exn_bt = Exn_bt.get exn in
+              let bt = Printexc.get_raw_backtrace () in
               Some
                 (fun k ->
-                  Collection.push t.ready (Raise (fiber, k, exn_bt));
+                  Collection.push t.ready (Raise (fiber, k, exn, bt));
                   next p t)
       end
     | Trigger.Await trigger ->
@@ -303,11 +308,19 @@ let rec run_fiber_on n fiber main runner_main context =
     in
     match run_fiber_on (n - 1) fiber main runner_main context with
     | result ->
-        Option.iter Exn_bt.raise (Domain.join runner);
+        begin
+          match Domain.join runner with
+          | None -> ()
+          | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
+        end;
         result
     | exception exn ->
         let bt = Printexc.get_raw_backtrace () in
-        Option.iter Exn_bt.raise (Domain.join runner);
+        begin
+          match Domain.join runner with
+          | None -> ()
+          | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
+        end;
         Printexc.raise_with_backtrace exn bt
 
 let run_fiber_on ?fatal_exn_handler ~n_domains fiber main =
@@ -321,7 +334,9 @@ let run_fiber_on ?fatal_exn_handler ~n_domains fiber main =
         Printexc.record_backtrace bt_status;
         match runner_on_this_thread context with
         | () -> None
-        | exception exn -> Some (Exn_bt.get exn)
+        | exception exn ->
+            let bt = Printexc.get_raw_backtrace () in
+            Some (exn, bt)
   in
 
   run_fiber_on n_domains fiber main runner_main context
diff --git a/lib/picos_randos/picos_randos.mli b/lib/picos_mux.random/picos_mux_random.mli
similarity index 92%
rename from lib/picos_randos/picos_randos.mli
rename to lib/picos_mux.random/picos_mux_random.mli
index 411c838..e35fe0e 100644
--- a/lib/picos_randos/picos_randos.mli
+++ b/lib/picos_mux.random/picos_mux_random.mli
@@ -4,9 +4,9 @@
     ℹ️ This scheduler implementation is specifically intended for testing
     libraries implemented in Picos.
 
-    ⚠️ This scheduler uses {!Picos_select} internally.  If running multiple
-    threads that each run this scheduler, {!Picos_select.configure} must be
-    called by the main thread before creating other threads.
+    ⚠️ This scheduler uses {!Picos_stdio_select} internally.  If running multiple
+    threads that each run this scheduler, {!Picos_stdio_select.configure} must
+    be called by the main thread before creating other threads.
 
     {!Picos} is an interface that allows schedulers to make scheduling decisions
     freely.  After each effect this scheduler picks the next fiber to run
diff --git a/lib/picos_randos/select.none.ml b/lib/picos_mux.random/select.none.ml
similarity index 100%
rename from lib/picos_randos/select.none.ml
rename to lib/picos_mux.random/select.none.ml
diff --git a/lib/picos_mux.random/select.some.ml b/lib/picos_mux.random/select.some.ml
new file mode 100644
index 0000000..e4981d3
--- /dev/null
+++ b/lib/picos_mux.random/select.some.ml
@@ -0,0 +1,2 @@
+let cancel_after = Picos_stdio_select.cancel_after
+let check_configured = Picos_stdio_select.check_configured
diff --git a/lib/picos_threaded/dune b/lib/picos_mux.thread/dune
similarity index 52%
rename from lib/picos_threaded/dune
rename to lib/picos_mux.thread/dune
index 0448355..8f24cb1 100644
--- a/lib/picos_threaded/dune
+++ b/lib/picos_mux.thread/dune
@@ -1,13 +1,12 @@
 (library
- (name picos_threaded)
- (public_name picos.threaded)
+ (name picos_mux_thread)
+ (public_name picos_mux.thread)
  (optional)
  (libraries
   (re_export picos)
-  (re_export picos.exn_bt)
   (select
    select.ml
    from
-   (picos.select -> select.some.ml)
+   (picos_stdio.select -> select.some.ml)
    (-> select.none.ml))
   threads.posix))
diff --git a/lib/picos_threaded/picos_threaded.ml b/lib/picos_mux.thread/picos_mux_thread.ml
similarity index 100%
rename from lib/picos_threaded/picos_threaded.ml
rename to lib/picos_mux.thread/picos_mux_thread.ml
diff --git a/lib/picos_threaded/picos_threaded.mli b/lib/picos_mux.thread/picos_mux_thread.mli
similarity index 87%
rename from lib/picos_threaded/picos_threaded.mli
rename to lib/picos_mux.thread/picos_mux_thread.mli
index 6441887..a61d1f2 100644
--- a/lib/picos_threaded/picos_threaded.mli
+++ b/lib/picos_mux.thread/picos_mux_thread.mli
@@ -3,8 +3,8 @@
     ℹ️ This scheduler implementation is mostly meant as an example and for use in
     testing libraries implemented in {!Picos}.
 
-    ⚠️ This scheduler uses {!Picos_select} internally.  If running multiple
-    threads that each run this scheduler, {!Picos_select.configure} must be
+    ⚠️ This scheduler uses {!Picos_stdio_select} internally.  If running multiple
+    threads that each run this scheduler, {!Picos_stdio_select.configure} must be
     called by the main thread before creating other threads.
 
     Briefly:
@@ -14,7 +14,7 @@
     - {{!Picos.Fiber.spawn} [spawn]} creates a new {!Thread} for each fiber.
     - {{!Picos.Fiber.yield} [yield]} just calls {!Thread.yield}.
     - {{!Picos.Computation.cancel_after} [cancel_after]} uses a per-domain
-      {{!Picos_select} background thread} that runs a {!Unix.select} loop to
+      {{!Picos_stdio_select} background thread} that runs a {!Unix.select} loop to
       cancel computations.
     - {{!Picos.Trigger.await} [await]} uses a per thread mutex and condition to
       suspend the thread.
diff --git a/lib/picos_threaded/select.none.ml b/lib/picos_mux.thread/select.none.ml
similarity index 100%
rename from lib/picos_threaded/select.none.ml
rename to lib/picos_mux.thread/select.none.ml
diff --git a/lib/picos_mux.thread/select.some.ml b/lib/picos_mux.thread/select.some.ml
new file mode 100644
index 0000000..e4981d3
--- /dev/null
+++ b/lib/picos_mux.thread/select.some.ml
@@ -0,0 +1,2 @@
+let cancel_after = Picos_stdio_select.cancel_after
+let check_configured = Picos_stdio_select.check_configured
diff --git a/lib/picos_mux/dune b/lib/picos_mux/dune
new file mode 100644
index 0000000..4d81e87
--- /dev/null
+++ b/lib/picos_mux/dune
@@ -0,0 +1,3 @@
+(documentation
+ (package picos_mux)
+ (mld_files index))
diff --git a/lib/picos_mux/index.mld b/lib/picos_mux/index.mld
new file mode 100644
index 0000000..61cb97d
--- /dev/null
+++ b/lib/picos_mux/index.mld
@@ -0,0 +1,13 @@
+{0 Sample schedulers for Picos}
+
+This package contains schedulers provided as samples for {!Picos}.
+
+{!modules:
+  Picos_mux_fifo
+  Picos_mux_multififo
+  Picos_mux_random
+  Picos_mux_thread
+}
+
+{^ You may find these useful for both understanding the core Picos interface and
+   for testing your own libraries implemented in Picos.}
diff --git a/lib/picos_randos/select.some.ml b/lib/picos_randos/select.some.ml
deleted file mode 100644
index aa39c6b..0000000
--- a/lib/picos_randos/select.some.ml
+++ /dev/null
@@ -1,2 +0,0 @@
-let cancel_after = Picos_select.cancel_after
-let check_configured = Picos_select.check_configured
diff --git a/lib/picos_rc/dune b/lib/picos_rc/dune
deleted file mode 100644
index 34bdc10..0000000
--- a/lib/picos_rc/dune
+++ /dev/null
@@ -1,4 +0,0 @@
-(library
- (name picos_rc)
- (public_name picos.rc)
- (libraries backoff picos.htbl))
diff --git a/lib/picos_select/dune b/lib/picos_select/dune
deleted file mode 100644
index e461783..0000000
--- a/lib/picos_select/dune
+++ /dev/null
@@ -1,35 +0,0 @@
-(library
- (name picos_select)
- (public_name picos.select)
- (optional)
- (libraries
-  (re_export picos)
-  (re_export picos.exn_bt)
-  (re_export picos.fd)
-  (re_export picos.sync)
-  (re_export unix)
-  picos.domain
-  picos.thread
-  picos.htbl
-  picos_thread_atomic
-  backoff
-  threads.posix
-  psq
-  multicore-magic
-  mtime
-  mtime.clock.os))
-
-(mdx
- (enabled_if
-  (>= %{ocaml_version} 5.1.0))
- (libraries
-  picos
-  picos.exn_bt
-  picos.finally
-  picos.randos
-  picos.select
-  picos.stdio
-  picos.structured
-  picos.sync
-  unix)
- (files picos_select.mli))
diff --git a/lib/picos_std.event/dune b/lib/picos_std.event/dune
new file mode 100644
index 0000000..20f5945
--- /dev/null
+++ b/lib/picos_std.event/dune
@@ -0,0 +1,4 @@
+(library
+ (name picos_std_event)
+ (public_name picos_std.event)
+ (libraries picos))
diff --git a/lib/picos_sync/event.ml b/lib/picos_std.event/event.ml
similarity index 91%
rename from lib/picos_sync/event.ml
rename to lib/picos_std.event/event.ml
index b2e5a92..0b2f4f2 100644
--- a/lib/picos_sync/event.ml
+++ b/lib/picos_std.event/event.ml
@@ -47,14 +47,15 @@ let sync_as : type a n. n -> (a, n) tycon -> a =
         if Computation.try_attach target t then
           match Trigger.await t with
           | None -> ()
-          | Some exn_bt ->
-              if Computation.try_cancel target exn_bt then Exn_bt.raise exn_bt
+          | Some (exn, bt) ->
+              if Computation.try_cancel target exn bt then
+                Printexc.raise_with_backtrace exn bt
       end;
       Computation.await target ()
   | exception exn ->
-      let exn_bt = Exn_bt.get exn in
-      Computation.cancel target exn_bt;
-      Exn_bt.raise exn_bt
+      let bt = Printexc.get_raw_backtrace () in
+      Computation.cancel target exn bt;
+      Printexc.raise_with_backtrace exn bt
 
 let guard create_event =
   let request target to_result =
diff --git a/lib/picos_std.event/picos_std_event.ml b/lib/picos_std.event/picos_std_event.ml
new file mode 100644
index 0000000..e8bc452
--- /dev/null
+++ b/lib/picos_std.event/picos_std_event.ml
@@ -0,0 +1,1 @@
+module Event = Event
diff --git a/lib/picos_std.event/picos_std_event.mli b/lib/picos_std.event/picos_std_event.mli
new file mode 100644
index 0000000..506db1f
--- /dev/null
+++ b/lib/picos_std.event/picos_std_event.mli
@@ -0,0 +1,113 @@
+(** Basic event abstraction for {!Picos}. *)
+
+module Event : sig
+  (** First-class synchronous communication abstraction.
+
+      Events describe a thing that might happen in the future, or a concurrent
+      offer or request that might be accepted or succeed, but is cancelable if
+      some other event happens first.
+
+      See the {!Picos_stdio_select} library for an example.
+
+      ℹ️ This module intentionally mimics the
+      {{:https://ocaml.org/manual/5.2/api/Event.html} [Event]} module provided
+      by the OCaml POSIX threads library. *)
+
+  type !'a t
+  (** An event returning a value of type ['a]. *)
+
+  type 'a event = 'a t
+  (** An alias for the {!Event.t} type to match the
+      {{:https://ocaml.org/manual/5.2/api/Event.html} [Event]} module
+      signature. *)
+
+  val always : 'a -> 'a t
+  (** [always value] returns an event that can always be committed to resulting
+      in the given [value]. *)
+
+  (** {2 Composing events} *)
+
+  val choose : 'a t list -> 'a t
+  (** [choose events] return an event that offers all of the given events and
+      then commits to at most one of them. *)
+
+  val wrap : 'b t -> ('b -> 'a) -> 'a t
+  (** [wrap event fn] returns an event that acts as the given [event] and then
+      applies the given function to the value in case the event is committed
+      to. *)
+
+  val map : ('b -> 'a) -> 'b t -> 'a t
+  (** [map fn event] is equivalent to {{!wrap} [wrap event fn]}. *)
+
+  val guard : (unit -> 'a t) -> 'a t
+  (** [guard thunk] returns an event that, when {{!sync} synchronized}, calls
+      the [thunk], and then behaves like the resulting event.
+
+      ⚠️ Raising an exception from a [guard thunk] will result in raising that
+      exception out of the {!sync}.  This may result in dropping the result of
+      an event that committed just after the exception was raised.  This means
+      that you should treat an unexpected exception raised from {!sync} as a
+      fatal error. *)
+
+  (** {2 Consuming events} *)
+
+  val sync : 'a t -> 'a
+  (** [sync event] synchronizes on the given event.
+
+      Synchronizing on an event executes in three phases:
+
+      {ol
+        {- In the first phase offers or requests are made to communicate.}
+        {- One of the offers or requests is committed to and all the other
+           offers and requests are canceled.}
+        {- A final result is computed from the value produced by the event.}}
+
+      ⚠️ [sync event] does not wait for the canceled concurrent requests to
+      terminate.  This means that you should arrange for guaranteed cleanup
+      through other means such as the use of {{!Picos_std_structured} structured
+      concurrency}. *)
+
+  val select : 'a t list -> 'a
+  (** [select events] is equivalent to {{!sync} [sync (choose events)]}. *)
+
+  (** {2 Primitive events}
+
+      ℹ️ The {{!Picos.Computation} [Computation]} concept of {!Picos} can be seen
+      as a basic single-shot atomic event.  This module builds on that concept
+      to provide a composable API to concurrent services exposed through
+      computations. *)
+
+  open Picos
+
+  type 'a request = {
+    request : 'r. (unit -> 'r) Computation.t -> ('a -> 'r) -> unit;
+  }
+  [@@unboxed]
+  (** Represents a function that requests a concurrent service to update a
+      {{!Picos.Computation} computation}.
+
+      ℹ️ The computation passed to a request may be completed by some other event
+      at any point.  All primitive requests should be implemented carefully to
+      take that into account.  If the computation is completed by some other
+      event, then the request should be considered as canceled, take no effect,
+      and not leak any resources.
+
+      ⚠️ Raising an exception from a [request] function will result in raising
+      that exception out of the {!sync}.  This may result in dropping the result
+      of an event that committed just after the exception was raised.  This
+      means that you should treat an unexpected exception raised from {!sync} as
+      a fatal error.  In addition, you should arrange for concurrent services to
+      report unexpected errors independently of the computation being passed to
+      the service. *)
+
+  val from_request : 'a request -> 'a t
+  (** [from_request { request }] creates an {{!Event} event} from the request
+      function. *)
+
+  val from_computation : 'a Computation.t -> 'a t
+  (** [from_computation source] creates an {{!Event} event} that can be
+      committed to once the given [source] computation has completed.
+
+      ℹ️ Committing to some other event does not cancel the [source]
+      computation. *)
+end
diff --git a/lib/picos_std.finally/dune b/lib/picos_std.finally/dune
new file mode 100644
index 0000000..fedc18f
--- /dev/null
+++ b/lib/picos_std.finally/dune
@@ -0,0 +1,14 @@
+(library
+ (name picos_std_finally)
+ (public_name picos_std.finally)
+ (libraries picos))
+
+(mdx
+ (package picos_meta)
+ (libraries
+  picos_std.finally
+  picos_std.structured
+  picos_std.sync
+  picos_stdio
+  unix)
+ (files picos_std_finally.mli))
diff --git a/lib/picos_finally/picos_finally.ml b/lib/picos_std.finally/picos_std_finally.ml
similarity index 98%
rename from lib/picos_finally/picos_finally.ml
rename to lib/picos_std.finally/picos_std_finally.ml
index 96e7044..a2bf058 100644
--- a/lib/picos_finally/picos_finally.ml
+++ b/lib/picos_std.finally/picos_std_finally.ml
@@ -57,10 +57,10 @@ let await_transferred_or_dropped instance =
       | None ->
           (* We release in case we could not wait. *)
           drop instance
-      | Some exn_bt ->
+      | Some (exn, bt) ->
           (* We have been canceled, so we release. *)
           drop instance;
-          Exn_bt.raise exn_bt
+          Printexc.raise_with_backtrace exn bt
     end
 
 let[@inline never] instantiate release acquire scope =
diff --git a/lib/picos_finally/picos_finally.mli b/lib/picos_std.finally/picos_std_finally.mli
similarity index 97%
rename from lib/picos_finally/picos_finally.mli
rename to lib/picos_std.finally/picos_std_finally.mli
index 96ba0e8..208d653 100644
--- a/lib/picos_finally/picos_finally.mli
+++ b/lib/picos_std.finally/picos_std_finally.mli
@@ -6,10 +6,10 @@
     We open both this library and a few other libraries
 
     {[
-      open Picos_finally
-      open Picos_structured
+      open Picos_std_finally
+      open Picos_std_structured
+      open Picos_std_sync
       open Picos_stdio
-      open Picos_sync
     ]}
 
     for the examples. *)
@@ -176,6 +176,6 @@ val move : 'r instance -> ('r -> 'a) -> 'a
         ()
     ]}
 
-    The above uses an {{!Picos_sync.Ivar} [Ivar]} to communicate the movable
+    The above uses an {{!Picos_std_sync.Ivar} [Ivar]} to communicate the movable
     resource from the child fiber to the parent fiber.  Any concurrency safe
     mechanism could be used. *)
diff --git a/lib/picos_structured/bundle.ml b/lib/picos_std.structured/bundle.ml
similarity index 86%
rename from lib/picos_structured/bundle.ml
rename to lib/picos_std.structured/bundle.ml
index 676f33a..d3b6884 100644
--- a/lib/picos_structured/bundle.ml
+++ b/lib/picos_std.structured/bundle.ml
@@ -22,23 +22,25 @@ let flock_key : [ `Bundle | `Nothing ] tdt Fiber.FLS.t = Fiber.FLS.create ()
 type t = [ `Bundle ] tdt
 
 let terminate ?callstack (Bundle { bundle = Packed bundle; _ } : t) =
-  Computation.cancel bundle (Control.terminate_bt ?callstack ())
+  Computation.cancel bundle Control.Terminate
+    (Control.get_callstack_opt callstack)
 
 let terminate_after ?callstack (Bundle { bundle = Packed bundle; _ } : t)
     ~seconds =
-  Computation.cancel_after bundle ~seconds (Control.terminate_bt ?callstack ())
+  Computation.cancel_after bundle ~seconds Control.Terminate
+    (Control.get_callstack_opt callstack)
 
-let error ?callstack (Bundle r as t : t) (exn_bt : Exn_bt.t) =
-  if exn_bt.Exn_bt.exn != Control.Terminate then begin
+let error ?callstack (Bundle r as t : t) exn bt =
+  if exn != Control.Terminate then begin
     terminate ?callstack t;
-    Control.Errors.push r.errors exn_bt
+    Control.Errors.push r.errors exn bt
   end
 
 let decr (Bundle r : t) =
   let n = Atomic.fetch_and_add r.config (-config_one) in
   if n < config_one * 2 then begin
     let (Packed bundle) = r.bundle in
-    Computation.cancel bundle (Control.terminate_bt ());
+    Computation.cancel bundle Control.Terminate Control.empty_bt;
     Trigger.signal r.finished
   end
 
@@ -119,10 +121,10 @@ let join_after_pass (type a) ?callstack ?on_return (fn : a -> _) (pass : a pass)
       await t fiber packed canceler outer pass;
       value
   | exception exn ->
-      let exn_bt = Exn_bt.get exn in
-      error t exn_bt;
+      let bt = Printexc.get_raw_backtrace () in
+      error t exn bt;
       await t fiber packed canceler outer pass;
-      Exn_bt.raise exn_bt
+      Printexc.raise_with_backtrace exn bt
 
 let rec incr (Bundle r as t : t) backoff =
   let before = Atomic.get r.config in
@@ -147,9 +149,9 @@ let fork_as_promise_pass (type a) (Bundle r as t : t) thunk (pass : a pass) =
               match thunk () with
               | value -> Computation.return child value
               | exception exn ->
-                  let exn_bt = Exn_bt.get exn in
-                  Computation.cancel child exn_bt;
-                  error (get_flock fiber) exn_bt
+                  let bt = Printexc.get_raw_backtrace () in
+                  Computation.cancel child exn bt;
+                  error (get_flock fiber) exn bt
             end;
             let (Bundle r as t : t) = get_flock fiber in
             let (Packed bundle) = r.bundle in
@@ -161,9 +163,9 @@ let fork_as_promise_pass (type a) (Bundle r as t : t) thunk (pass : a pass) =
               match thunk () with
               | value -> Computation.return child value
               | exception exn ->
-                  let exn_bt = Exn_bt.get exn in
-                  Computation.cancel child exn_bt;
-                  error t exn_bt
+                  let bt = Printexc.get_raw_backtrace () in
+                  Computation.cancel child exn bt;
+                  error t exn bt
             end;
             let (Packed bundle) = r.bundle in
             Computation.detach bundle canceler;
@@ -190,13 +192,15 @@ let fork_pass (type a) (Bundle r as t : t) thunk (pass : a pass) =
           fun fiber ->
             begin
               try thunk ()
-              with exn -> error (get_flock fiber) (Exn_bt.get exn)
+              with exn ->
+                error (get_flock fiber) exn (Printexc.get_raw_backtrace ())
             end;
             decr (get_flock fiber)
       | Arg ->
           fun _ ->
             begin
-              try thunk () with exn -> error t (Exn_bt.get exn)
+              try thunk ()
+              with exn -> error t exn (Printexc.get_raw_backtrace ())
             end;
             decr t
     in
diff --git a/lib/picos_structured/control.ml b/lib/picos_std.structured/control.ml
similarity index 67%
rename from lib/picos_structured/control.ml
rename to lib/picos_std.structured/control.ml
index a9c2fee..5a02962 100644
--- a/lib/picos_structured/control.ml
+++ b/lib/picos_std.structured/control.ml
@@ -5,50 +5,49 @@ let[@inline never] forbidden () = invalid_arg "cancelation forbidden"
 
 exception Terminate
 
-let terminate_bt = Exn_bt.get_callstack 0 Terminate
+let empty_bt = Printexc.get_callstack 0
 
-let terminate_bt ?callstack () =
-  match callstack with
-  | None -> terminate_bt
-  | Some n -> Exn_bt.get_callstack n Terminate
+let[@inline] get_callstack_opt = function
+  | None | Some 0 -> empty_bt
+  | Some n -> Printexc.get_callstack n
 
-exception Errors of Exn_bt.t list
+exception Errors of (exn * Printexc.raw_backtrace) list
 
 let () =
   Printexc.register_printer @@ function
   | Errors exn_bts ->
       let causes =
-        List.map (fun exn_bt -> Printexc.to_string exn_bt.Exn_bt.exn) exn_bts
+        List.map (fun (exn, _) -> Printexc.to_string exn) exn_bts
         |> String.concat "; "
       in
       Some (Printf.sprintf "Errors[%s]" causes)
   | _ -> None
 
 module Errors = struct
-  type t = Exn_bt.t list Atomic.t
+  type t = (exn * Printexc.raw_backtrace) list Atomic.t
 
   let create () = Atomic.make []
 
-  let rec check (exn_bts : Exn_bt.t list) exns =
+  let rec check (exn_bts : (exn * Printexc.raw_backtrace) list) exns =
     match exn_bts with
     | [] -> ()
-    | [ exn_bt ] ->
-        Printexc.raise_with_backtrace (Errors (exn_bt :: exns)) exn_bt.bt
+    | [ ((_, bt) as exn_bt) ] ->
+        Printexc.raise_with_backtrace (Errors (exn_bt :: exns)) bt
     | exn_bt :: exn_bts -> check exn_bts (exn_bt :: exns)
 
   let check t =
     match Atomic.get t with
     | [] -> ()
-    | [ exn_bt ] -> Exn_bt.raise exn_bt
+    | [ (exn, bt) ] -> Printexc.raise_with_backtrace exn bt
     | exn_bts -> check exn_bts []
 
-  let rec push t exn_bt backoff =
+  let rec push t exn bt backoff =
     let before = Atomic.get t in
-    let after = exn_bt :: before in
+    let after = (exn, bt) :: before in
     if not (Atomic.compare_and_set t before after) then
-      push t exn_bt (Backoff.once backoff)
+      push t exn bt (Backoff.once backoff)
 
-  let push t exn_bt = push t exn_bt Backoff.default
+  let push t exn bt = push t exn bt Backoff.default
 end
 
 let raise_if_canceled () = Fiber.check (Fiber.current ())
@@ -60,7 +59,7 @@ let block () =
   if Fiber.has_forbidden fiber then forbidden ();
   match Trigger.await (Trigger.create ()) with
   | None -> finished ()
-  | Some exn_bt -> Exn_bt.raise exn_bt
+  | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
 
 let protect thunk = Fiber.forbid (Fiber.current ()) thunk
 
@@ -75,7 +74,8 @@ let terminate_after ?callstack ~seconds thunk =
      [match ... with] below. *)
   Fiber.set_computation fiber into_packed;
   match
-    Computation.cancel_after into ~seconds (terminate_bt ?callstack ());
+    Computation.cancel_after into ~seconds Terminate
+      (get_callstack_opt callstack);
     thunk ()
   with
   | result ->
@@ -85,9 +85,9 @@ let terminate_after ?callstack ~seconds thunk =
       Fiber.set_computation fiber packed;
       result
   | exception exn ->
-      let exn_bt = Exn_bt.get exn in
-      Computation.cancel into exn_bt;
+      let bt = Printexc.get_raw_backtrace () in
+      Computation.cancel into exn bt;
       let (Packed from) = packed in
       Computation.detach from canceler;
       Fiber.set_computation fiber packed;
-      Exn_bt.raise exn_bt
+      Printexc.raise_with_backtrace exn bt
diff --git a/lib/picos_std.structured/dune b/lib/picos_std.structured/dune
new file mode 100644
index 0000000..3d8bd86
--- /dev/null
+++ b/lib/picos_std.structured/dune
@@ -0,0 +1,25 @@
+(library
+ (name picos_std_structured)
+ (public_name picos_std.structured)
+ (libraries
+  (re_export picos_std.event)
+  picos
+  backoff
+  multicore-magic))
+
+(mdx
+ (package picos_meta)
+ (enabled_if
+  (and
+   (>= %{ocaml_version} 5.0.0)
+   (= %{env:OPAM_REPO_CI=false} false)))
+ (libraries
+  picos_mux.random
+  picos_mux.fifo
+  picos_std.event
+  picos_std.finally
+  picos_std.structured
+  picos_std.sync
+  picos_stdio
+  unix)
+ (files picos_std_structured.mli))
diff --git a/lib/picos_structured/flock.ml b/lib/picos_std.structured/flock.ml
similarity index 100%
rename from lib/picos_structured/flock.ml
rename to lib/picos_std.structured/flock.ml
diff --git a/lib/picos_structured/picos_structured.ml b/lib/picos_std.structured/picos_std_structured.ml
similarity index 100%
rename from lib/picos_structured/picos_structured.ml
rename to lib/picos_std.structured/picos_std_structured.ml
diff --git a/lib/picos_structured/picos_structured.mli b/lib/picos_std.structured/picos_std_structured.mli
similarity index 89%
rename from lib/picos_structured/picos_structured.mli
rename to lib/picos_std.structured/picos_std_structured.mli
index 29bf30d..a62b4cf 100644
--- a/lib/picos_structured/picos_structured.mli
+++ b/lib/picos_std.structured/picos_std_structured.mli
@@ -6,14 +6,14 @@
     For the {{!examples} examples} we open some modules:
 
     {[
-      open Picos_finally
-      open Picos_structured
+      open Picos_std_event
+      open Picos_std_finally
+      open Picos_std_structured
+      open Picos_std_sync
       open Picos_stdio
-      open Picos_sync
     ]} *)
 
-open Picos
-open Picos_sync
+open Picos_std_event
 
 (** {1 Modules} *)
 
@@ -24,7 +24,7 @@ module Control : sig
   (** An exception that is used to signal fibers, typically by canceling them,
       that they should terminate by letting the exception propagate.
 
-      ℹ️ Within {{!Picos_structured} this library}, the [Terminate] exception
+      ℹ️ Within {{!Picos_std_structured} this library}, the [Terminate] exception
       does not, by itself, indicate an error.  Raising it inside a fiber forked
       within the structured concurrency constructs of this library simply causes
       the relevant part of the tree of fibers to be terminated.
@@ -34,20 +34,20 @@ module Control : sig
       then, of course, raise the [Terminate] exception after all of the fibers
       have been terminated. *)
 
-  exception Errors of Exn_bt.t list
+  exception Errors of (exn * Printexc.raw_backtrace) list
   (** An exception that can be used to collect exceptions, typically indicating
       errors, from multiple fibers.
 
       ℹ️ The {!Terminate} exception is not considered an error within
-      {{!Picos_structured} this library} and the structuring constructs do not
-      include it in the list of [Errors]. *)
+      {{!Picos_std_structured} this library} and the structuring constructs do
+      not include it in the list of [Errors]. *)
 
   val raise_if_canceled : unit -> unit
   (** [raise_if_canceled ()] checks whether the current fiber has been canceled
       and if so raises the exception that the fiber was canceled with.
 
-      ℹ️ Within {{!Picos_structured} this library} fibers are canceled using the
-      {!Terminate} exception. *)
+      ℹ️ Within {{!Picos_std_structured} this library} fibers are canceled using
+      the {!Terminate} exception. *)
 
   val yield : unit -> unit
   (** [yield ()] asks the current fiber to be rescheduled. *)
@@ -60,10 +60,10 @@ module Control : sig
   (** [protect thunk] forbids propagation of cancelation for the duration of
       [thunk ()].
 
-      ℹ️ With the constructs provided by {{!Picos_structured} this library} it is
-      not possible to prevent a fiber from being canceled, but it is possible
-      for a fiber to forbid the scheduler from propagating cancelation to the
-      fiber. *)
+      ℹ️ With the constructs provided by {{!Picos_std_structured} this library}
+      it is not possible to prevent a fiber from being canceled, but it is
+      possible for a fiber to forbid the scheduler from propagating cancelation
+      to the fiber. *)
 
   val block : unit -> 'a
   (** [block ()] suspends the current fiber until it is canceled at which point
@@ -82,8 +82,8 @@ module Control : sig
 
       Using [terminate_after] one can attempt any blocking operation that
       supports cancelation with a timeout.  For example, one could try to
-      {{!Picos_sync.Ivar.read} [read]} an {{!Picos_sync.Ivar} [Ivar]} with a
-      timeout
+      {{!Picos_std_sync.Ivar.read} [read]} an {{!Picos_std_sync.Ivar} [Ivar]}
+      with a timeout
 
       {[
         let peek_in ~seconds ivar =
@@ -150,8 +150,8 @@ module Promise : sig
       canceled. *)
 
   val completed : 'a t -> 'a Event.t
-  (** [completed promise] returns an {{!Picos_sync.Event} event} that can be
-      committed to once the promise has completed. *)
+  (** [completed promise] returns an {{!Picos_std_event.Event} event} that can
+      be committed to once the promise has completed. *)
 
   val is_running : 'a t -> bool
   (** [is_running promise] determines whether the completion of the promise is
@@ -232,8 +232,8 @@ module Bundle : sig
   (** [terminate_after ~seconds bundle] arranges to {!terminate} the bundle
       after the specified timeout in [seconds]. *)
 
-  val error : ?callstack:int -> t -> Exn_bt.t -> unit
-  (** [error bundle exn_bt] first calls {!terminate} and then adds the exception
+  val error : ?callstack:int -> t -> exn -> Printexc.raw_backtrace -> unit
+  (** [error bundle exn bt] first calls {!terminate} and then adds the exception
       with backtrace to the list of exceptions to be raised, unless the
       exception is the {{!Control.Terminate} [Terminate]} exception, which is
       not considered to signal an error by itself.
@@ -310,8 +310,8 @@ module Flock : sig
   (** [terminate_after ~seconds ()] arranges to {!terminate} the current flock
       after the specified timeout in [seconds]. *)
 
-  val error : ?callstack:int -> Exn_bt.t -> unit
-  (** [error exn_bt] first calls {!terminate} and then adds the exception with
+  val error : ?callstack:int -> exn -> Printexc.raw_backtrace -> unit
+  (** [error exn bt] first calls {!terminate} and then adds the exception with
       backtrace to the list of exceptions to be raised, unless the exception is
       the {{!Control.Terminate} [Terminate]} exception, which is not considered
       to signal an error by itself.
@@ -471,9 +471,9 @@ end
         end
     ]}
 
-    First of all, note that above the {{!Picos_sync.Mutex} [Mutex]},
-    {{!Picos_sync.Condition} [Condition]}, and {{!Picos_sync.Semaphore}
-    [Semaphore]} modules come from the {!Picos_sync} library and the
+    First of all, note that above the {{!Picos_std_sync.Mutex} [Mutex]},
+    {{!Picos_std_sync.Condition} [Condition]}, and {{!Picos_std_sync.Semaphore}
+    [Semaphore]} modules come from the {!Picos_std_sync} library and the
     {{!Picos_stdio.Unix} [Unix]} module comes from the {!Picos_stdio} library.
     They do not come from the standard OCaml libraries.
 
@@ -483,18 +483,19 @@ end
 
     - {!Control.block} never returns,
     - {!Promise.await} never returns as the promise won't be completed,
-    - {{!Picos_sync.Condition.wait} [Condition.wait]} never returns, because the
-      condition is never signaled,
-    - {{!Picos_sync.Semaphore.Binary.acquire} [Semaphore.Binary.acquire]} and
-      {{!Picos_sync.Semaphore.Counting.acquire} [Semaphore.Counting.acquire]}
-      never return, because the counts of the semaphores never change from [0],
-    - {{!Picos_sync.Event.sync} [Event.sync]} never returns, because the event
-      can never be committed to,
-    - {{!Picos_sync.Latch.await} [Latch.await]} never returns, because the count
-      of the latch never reaches [0],
-    - {{!Picos_sync.Ivar.read} [Ivar.read]} never returns, because the
+    - {{!Picos_std_sync.Condition.wait} [Condition.wait]} never returns, because
+      the condition is never signaled,
+    - {{!Picos_std_sync.Semaphore.Binary.acquire} [Semaphore.Binary.acquire]}
+      and {{!Picos_std_sync.Semaphore.Counting.acquire}
+      [Semaphore.Counting.acquire]} never return, because the counts of the
+      semaphores never change from [0],
+    - {{!Picos_std_event.Event.sync} [Event.sync]} never returns, because the
+      event can never be committed to,
+    - {{!Picos_std_sync.Latch.await} [Latch.await]} never returns, because the
+      count of the latch never reaches [0],
+    - {{!Picos_std_sync.Ivar.read} [Ivar.read]} never returns, because the
       incremental variable is never filled,
-    - {{!Picos_sync.Stream.read} [Stream.read]} never returns, because the
+    - {{!Picos_std_sync.Stream.read} [Stream.read]} never returns, because the
       stream is never pushed to,
     - {{!Picos_stdio.Unix.read} [Unix.read]} never returns, because the socket
       is never written to, and the
@@ -506,7 +507,7 @@ end
     immediately and without leaking or leaving anything in an invalid state:
 
     {[
-      # Picos_randos.run_on ~n_domains:2 main
+      # Picos_mux_random.run_on ~n_domains:2 main
       - : unit = ()
     ]}
 
@@ -559,13 +560,13 @@ end
         Latch.decr latch
     ]}
 
-    The above program starts three fibers and uses a {{!Picos_sync.Latch} latch}
-    to ensure that all of them have been started, before two of them raise
-    errors and the third raises {{!Control.Terminate} [Terminate]}, which is not
-    considered an error in this library.  Running the program
+    The above program starts three fibers and uses a {{!Picos_std_sync.Latch}
+    latch} to ensure that all of them have been started, before two of them
+    raise errors and the third raises {{!Control.Terminate} [Terminate]}, which
+    is not considered an error in this library.  Running the program
 
     {[
-      # Picos_fifos.run many_errors
+      # Picos_mux_fifo.run many_errors
       Exception: Errors[Stdlib.Exit; Not_found]
     ]}
 
@@ -686,7 +687,7 @@ end
     Finally we run the main program with a scheduler:
 
     {[
-      # Picos_randos.run_on ~n_domains:1 main
+      # Picos_mux_random.run_on ~n_domains:1 main
       Received: Hello!
       Received: Hello!
       Received: Hello!
diff --git a/lib/picos_structured/promise.ml b/lib/picos_std.structured/promise.ml
similarity index 61%
rename from lib/picos_structured/promise.ml
rename to lib/picos_std.structured/promise.ml
index c9d3a2f..a464095 100644
--- a/lib/picos_structured/promise.ml
+++ b/lib/picos_std.structured/promise.ml
@@ -1,5 +1,5 @@
 open Picos
-open Picos_sync
+open Picos_std_event
 
 type 'a t = 'a Computation.t
 
@@ -9,9 +9,11 @@ let completed = Event.from_computation
 let is_running = Computation.is_running
 
 let try_terminate ?callstack t =
-  Computation.try_cancel t (Control.terminate_bt ?callstack ())
+  Computation.try_cancel t Control.Terminate
+    (Control.get_callstack_opt callstack)
 
 let terminate ?callstack t = try_terminate ?callstack t |> ignore
 
 let terminate_after ?callstack t ~seconds =
-  Computation.cancel_after t ~seconds (Control.terminate_bt ?callstack ())
+  Computation.cancel_after t ~seconds Control.Terminate
+    (Control.get_callstack_opt callstack)
diff --git a/lib/picos_structured/run.ml b/lib/picos_std.structured/run.ml
similarity index 80%
rename from lib/picos_structured/run.ml
rename to lib/picos_std.structured/run.ml
index b7e430a..bc9122f 100644
--- a/lib/picos_structured/run.ml
+++ b/lib/picos_std.structured/run.ml
@@ -2,7 +2,7 @@ open Picos
 
 let wrap_all t main _ =
   if Bundle.is_running t then begin
-    try main () with exn -> Bundle.error t (Exn_bt.get exn)
+    try main () with exn -> Bundle.error t exn (Printexc.get_raw_backtrace ())
   end;
   Bundle.decr t
 
@@ -10,7 +10,7 @@ let wrap_any t main _ =
   if Bundle.is_running t then begin
     match main () with
     | () -> Bundle.terminate t
-    | exception exn -> Bundle.error t (Exn_bt.get exn)
+    | exception exn -> Bundle.error t exn (Printexc.get_raw_backtrace ())
   end;
   Bundle.decr t
 
@@ -31,9 +31,9 @@ let run actions wrap =
   Bundle.join_after @@ fun (Bundle _ as t : Bundle.t) ->
   try spawn t wrap actions
   with exn ->
-    let exn_bt = Exn_bt.get exn in
+    let bt = Printexc.get_raw_backtrace () in
     Bundle.decr t;
-    Bundle.error t exn_bt
+    Bundle.error t exn bt
 
 let all actions = run actions wrap_all
 let any actions = run actions wrap_any
diff --git a/lib/picos_sync/condition.ml b/lib/picos_std.sync/condition.ml
similarity index 96%
rename from lib/picos_sync/condition.ml
rename to lib/picos_std.sync/condition.ml
index 5fc6592..b78ef36 100644
--- a/lib/picos_sync/condition.ml
+++ b/lib/picos_std.sync/condition.ml
@@ -49,9 +49,9 @@ let rec wait (t : t) mutex trigger fiber backoff =
     Fiber.set fiber ~forbid;
     match result with
     | None -> ()
-    | Some exn_bt ->
+    | Some (exn, bt) ->
         cleanup Backoff.default trigger t;
-        Exn_bt.raise exn_bt
+        Printexc.raise_with_backtrace exn bt
   end
   else wait t mutex trigger fiber (Backoff.once backoff)
 
diff --git a/lib/picos_std.sync/dune b/lib/picos_std.sync/dune
new file mode 100644
index 0000000..39b0cbf
--- /dev/null
+++ b/lib/picos_std.sync/dune
@@ -0,0 +1,15 @@
+(library
+ (name picos_std_sync)
+ (public_name picos_std.sync)
+ (libraries
+  (re_export picos_std.event)
+  picos
+  backoff
+  multicore-magic))
+
+(mdx
+ (package picos_meta)
+ (enabled_if
+  (>= %{ocaml_version} 5.0.0))
+ (libraries picos_mux.fifo picos_std.structured picos_std.sync)
+ (files picos_std_sync.mli))
diff --git a/lib/picos_sync/ivar.ml b/lib/picos_std.sync/ivar.ml
similarity index 95%
rename from lib/picos_sync/ivar.ml
rename to lib/picos_std.sync/ivar.ml
index 2d704f1..a2b7ce7 100644
--- a/lib/picos_sync/ivar.ml
+++ b/lib/picos_std.sync/ivar.ml
@@ -1,4 +1,5 @@
 open Picos
+open Picos_std_event
 
 type 'a t = 'a Computation.t
 
diff --git a/lib/picos_sync/latch.ml b/lib/picos_std.sync/latch.ml
similarity index 98%
rename from lib/picos_sync/latch.ml
rename to lib/picos_std.sync/latch.ml
index 208ed47..f737913 100644
--- a/lib/picos_sync/latch.ml
+++ b/lib/picos_std.sync/latch.ml
@@ -1,4 +1,5 @@
 open Picos
+open Picos_std_event
 
 let[@inline never] negative_count () = invalid_arg "negative count"
 let[@inline never] zero_count () = invalid_arg "zero count"
diff --git a/lib/picos_sync/lazy.ml b/lib/picos_std.sync/lazy.ml
similarity index 96%
rename from lib/picos_sync/lazy.ml
rename to lib/picos_std.sync/lazy.ml
index 2ad92d0..7b929af 100644
--- a/lib/picos_sync/lazy.ml
+++ b/lib/picos_std.sync/lazy.ml
@@ -56,9 +56,9 @@ let rec force t fiber backoff =
         if Atomic.compare_and_set t before after then begin
           match Trigger.await trigger with
           | None -> force t fiber Backoff.default
-          | Some exn_bt ->
+          | Some (exn, bt) ->
               cleanup t trigger Backoff.default;
-              Exn_bt.raise exn_bt
+              Printexc.raise_with_backtrace exn bt
         end
         else force t fiber (Backoff.once backoff)
 
diff --git a/lib/picos_sync/list_ext.ml b/lib/picos_std.sync/list_ext.ml
similarity index 100%
rename from lib/picos_sync/list_ext.ml
rename to lib/picos_std.sync/list_ext.ml
diff --git a/lib/picos_sync/mutex.ml b/lib/picos_std.sync/mutex.ml
similarity index 97%
rename from lib/picos_sync/mutex.ml
rename to lib/picos_std.sync/mutex.ml
index 04b54b1..d82b2e4 100644
--- a/lib/picos_sync/mutex.ml
+++ b/lib/picos_std.sync/mutex.ml
@@ -78,9 +78,9 @@ let rec lock_as fiber t entry backoff =
         if Atomic.compare_and_set t before after then begin
           match Trigger.await entry_r.trigger with
           | None -> ()
-          | Some exn_bt ->
+          | Some (exn, bt) ->
               cleanup_as entry t Backoff.default;
-              Exn_bt.raise exn_bt
+              Printexc.raise_with_backtrace exn bt
         end
         else lock_as fiber t entry (Backoff.once backoff)
       else owner ()
diff --git a/lib/picos_sync/picos_sync.ml b/lib/picos_std.sync/picos_std_sync.ml
similarity index 88%
rename from lib/picos_sync/picos_sync.ml
rename to lib/picos_std.sync/picos_std_sync.ml
index 8f8d652..007b8cf 100644
--- a/lib/picos_sync/picos_sync.ml
+++ b/lib/picos_std.sync/picos_std_sync.ml
@@ -2,7 +2,6 @@ module Mutex = Mutex
 module Condition = Condition
 module Semaphore = Semaphore
 module Lazy = Lazy
-module Event = Event
 module Latch = Latch
 module Ivar = Ivar
 module Stream = Stream
diff --git a/lib/picos_sync/picos_sync.mli b/lib/picos_std.sync/picos_std_sync.mli
similarity index 77%
rename from lib/picos_sync/picos_sync.mli
rename to lib/picos_std.sync/picos_std_sync.mli
index 7af0ebe..6b8d21b 100644
--- a/lib/picos_sync/picos_sync.mli
+++ b/lib/picos_std.sync/picos_std_sync.mli
@@ -7,11 +7,11 @@
     For the {{!examples} examples} we open some modules:
 
     {[
-      open Picos_structured
-      open Picos_sync
+      open Picos_std_structured
+      open Picos_std_sync
     ]} *)
 
-open Picos
+open Picos_std_event
 
 (** {1 Modules} *)
 
@@ -223,118 +223,6 @@ module Lazy : sig
       ]} *)
 end
 
-module Event : sig
-  (** First-class synchronous communication abstraction.
-
-      Events describe a thing that might happen in the future, or a concurrent
-      offer or request that might be accepted or succeed, but is cancelable if
-      some other event happens first.
-
-      See the {!Picos_select} library for an example.
-
-      ℹ️ This module intentionally mimics the
-      {{:https://ocaml.org/manual/5.2/api/Event.html} [Event]} module provided
-      by the OCaml POSIX threads library. *)
-
-  type !'a t
-  (** An event returning a value of type ['a]. *)
-
-  type 'a event = 'a t
-  (** An alias for the {!Event.t} type to match the
-      {{:https://ocaml.org/manual/5.2/api/Event.html} [Event]} module
-      signature. *)
-
-  val always : 'a -> 'a t
-  (** [always value] returns an event that can always be committed to resulting
-      in the given [value]. *)
-
-  (** {2 Composing events} *)
-
-  val choose : 'a t list -> 'a t
-  (** [choose events] return an event that offers all of the given events and
-      then commits to at most one of them. *)
-
-  val wrap : 'b t -> ('b -> 'a) -> 'a t
-  (** [wrap event fn] returns an event that acts as the given [event] and then
-      applies the given function to the value in case the event is committed
-      to. *)
-
-  val map : ('b -> 'a) -> 'b t -> 'a t
-  (** [map fn event] is equivalent to {{!wrap} [wrap event fn]}. *)
-
-  val guard : (unit -> 'a t) -> 'a t
-  (** [guard thunk] returns an event that, when {{!sync} synchronized}, calls
-      the [thunk], and then behaves like the resulting event.
-
-      ⚠️ Raising an exception from a [guard thunk] will result in raising that
-      exception out of the {!sync}.  This may result in dropping the result of
-      an event that committed just after the exception was raised.  This means
-      that you should treat an unexpected exception raised from {!sync} as a
-      fatal error. *)
-
-  (** {2 Consuming events} *)
-
-  val sync : 'a t -> 'a
-  (** [sync event] synchronizes on the given event.
-
-      Synchronizing on an event executes in three phases:
-
-      {ol
-        {- In the first phase offers or requests are made to communicate.}
-        {- One of the offers or requests is committed to and all the other
-           offers and requests are canceled.}
-        {- A final result is computed from the value produced by the event.}}
-
-      ⚠️ [sync event] does not wait for the canceled concurrent requests to
-      terminate.  This means that you should arrange for guaranteed cleanup
-      through other means such as the use of {{!Picos_structured} structured
-      concurrency}. *)
-
-  val select : 'a t list -> 'a
-  (** [select events] is equivalent to {{!sync} [sync (choose events)]}. *)
-
-  (** {2 Primitive events}
-
-      ℹ️ The {{!Picos.Computation} [Computation]} concept of {!Picos} can be seen
-      as a basic single-shot atomic event.  This module builds on that concept
-      to provide a composable API to concurrent services exposed through
-      computations. *)
-
-  open Picos
-
-  type 'a request = {
-    request : 'r. (unit -> 'r) Computation.t -> ('a -> 'r) -> unit;
-  }
-  [@@unboxed]
-  (** Represents a function that requests a concurrent service to update a
-      {{!Picos.Computation} computation}.
-
-      ℹ️ The computation passed to a request may be completed by some other event
-      at any point.  All primitive requests should be implemented carefully to
-      take that into account.  If the computation is completed by some other
-      event, then the request should be considered as canceled, take no effect,
-      and not leak any resources.
-
-      ⚠️ Raising an exception from a [request] function will result in raising
-      that exception out of the {!sync}.  This may result in dropping the result
-      of an event that committed just after the exception was raised.  This
-      means that you should treat an unexpected exception raised from {!sync} as
-      a fatal error.  In addition, you should arrange for concurrent services to
-      report unexpected errors independently of the computation being passed to
-      the service. *)
-
-  val from_request : 'a request -> 'a t
-  (** [from_request { request }] creates an {{!Event} event} from the request
-      function. *)
-
-  val from_computation : 'a Computation.t -> 'a t
-  (** [from_computation source] creates an {{!Event} event} that can be
-      committed to once the given [source] computation has completed.
-
-      ℹ️ Committing to some other event does not cancel the [source]
-      computation. *)
-end
-
 module Latch : sig
   (** A dynamic single-use countdown latch.
 
@@ -417,15 +305,15 @@ module Ivar : sig
   (** [fill ivar value] is equivalent to
       {{!try_fill} [try_fill ivar value |> ignore]}. *)
 
-  val try_poison : 'a t -> Exn_bt.t -> bool
-  (** [try_poison ivar exn_bt] attempts to poison the incremental variable with
+  val try_poison : 'a t -> exn -> Printexc.raw_backtrace -> bool
+  (** [try_poison ivar exn bt] attempts to poison the incremental variable with
       the specified exception and backtrace.  Returns [true] on success and
       [false] in case the variable had already been poisoned or assigned a
       value. *)
 
-  val poison : 'a t -> Exn_bt.t -> unit
-  (** [poison ivar exn_bt] is equivalent to
-      {{!try_poison} [try_poison ivar exn_bt |> ignore]}. *)
+  val poison : 'a t -> exn -> Printexc.raw_backtrace -> unit
+  (** [poison ivar exn bt] is equivalent to
+      {{!try_poison} [try_poison ivar exn bt |> ignore]}. *)
 
   val peek_opt : 'a t -> 'a option
   (** [peek_opt ivar] either returns [Some value] in case the variable has been
@@ -463,8 +351,8 @@ module Stream : sig
       has been {{!poison} poisoned} in which case only the exception given to
       {!poison} will be raised. *)
 
-  val poison : 'a t -> Exn_bt.t -> unit
-  (** [poison stream exn_bt] marks the stream as poisoned at the current
+  val poison : 'a t -> exn -> Printexc.raw_backtrace -> unit
+  (** [poison stream exn bt] marks the stream as poisoned at the current
       position, which means that subsequent attempts to {!push} to the [stream]
       will raise the given exception with backtrace. *)
 
@@ -559,10 +447,11 @@ end
     ]}
 
     The above is definitely not the fastest nor the most scalable bounded queue,
-    but we can now demonstrate it with the cooperative {!Picos_fifos} scheduler:
+    but we can now demonstrate it with the cooperative {!Picos_mux_fifo}
+    scheduler:
 
     {[
-      # Picos_fifos.run @@ fun () ->
+      # Picos_mux_fifo.run @@ fun () ->
 
         let bq =
           Bounded_q.create ~capacity:3
diff --git a/lib/picos_sync/q.ml b/lib/picos_std.sync/q.ml
similarity index 100%
rename from lib/picos_sync/q.ml
rename to lib/picos_std.sync/q.ml
diff --git a/lib/picos_sync/semaphore.ml b/lib/picos_std.sync/semaphore.ml
similarity index 95%
rename from lib/picos_sync/semaphore.ml
rename to lib/picos_std.sync/semaphore.ml
index e7a10d7..97a4db7 100644
--- a/lib/picos_sync/semaphore.ml
+++ b/lib/picos_std.sync/semaphore.ml
@@ -53,9 +53,9 @@ module Counting = struct
         if Atomic.compare_and_set t before (Obj.repr after) then begin
           match Trigger.await trigger with
           | None -> ()
-          | Some exn_bt ->
+          | Some (exn, bt) ->
               cleanup t trigger Backoff.default;
-              Exn_bt.raise exn_bt
+              Printexc.raise_with_backtrace exn bt
         end
         else acquire t (Backoff.once backoff)
     else
@@ -64,9 +64,9 @@ module Counting = struct
       if Atomic.compare_and_set t before (Obj.repr after) then begin
         match Trigger.await trigger with
         | None -> ()
-        | Some exn_bt ->
+        | Some (exn, bt) ->
             cleanup t trigger Backoff.default;
-            Exn_bt.raise exn_bt
+            Printexc.raise_with_backtrace exn bt
       end
       else acquire t (Backoff.once backoff)
 
diff --git a/lib/picos_sync/stream.ml b/lib/picos_std.sync/stream.ml
similarity index 85%
rename from lib/picos_sync/stream.ml
rename to lib/picos_std.sync/stream.ml
index c205641..b564750 100644
--- a/lib/picos_sync/stream.ml
+++ b/lib/picos_std.sync/stream.ml
@@ -1,4 +1,5 @@
 open Picos
+open Picos_std_event
 
 type 'a cursor = Cons of ('a * 'a cursor) Computation.t [@@unboxed]
 type 'a t = 'a cursor Atomic.t
@@ -27,18 +28,18 @@ let push t value =
   let next = (value, Cons (Computation.create ~mode:`LIFO ())) in
   push t next Backoff.default
 
-let rec poison t exn_bt backoff =
+let rec poison t exn bt backoff =
   let (Cons tail) = Atomic.get t in
-  if not (Computation.try_cancel tail exn_bt) then begin
+  if not (Computation.try_cancel tail exn bt) then begin
     let backoff = Backoff.once backoff in
     help t tail;
-    poison t exn_bt backoff
+    poison t exn bt backoff
   end
 
 let peek_opt (Cons at) =
   if Computation.is_running at then None else Some (Computation.await at)
 
-let poison t exn_bt = poison t exn_bt Backoff.default
+let poison t exn bt = poison t exn bt Backoff.default
 let tap = Atomic.get
 let read (Cons at) = Computation.await at
 let read_evt (Cons at) = Event.from_computation at
diff --git a/lib/picos_std/dune b/lib/picos_std/dune
new file mode 100644
index 0000000..c5b3e95
--- /dev/null
+++ b/lib/picos_std/dune
@@ -0,0 +1,3 @@
+(documentation
+ (package picos_std)
+ (mld_files index))
diff --git a/lib/picos_std/index.mld b/lib/picos_std/index.mld
new file mode 100644
index 0000000..287c8ca
--- /dev/null
+++ b/lib/picos_std/index.mld
@@ -0,0 +1,17 @@
+{0 Sample libraries for Picos}
+
+This package contains sample scheduler agnostic libraries for {!Picos}.  Many of
+the modules are intentionally designed to mimic modules from the OCaml Stdlib.
+
+{!modules:
+  Picos_std_finally
+  Picos_std_event
+  Picos_std_structured
+  Picos_std_sync
+}
+
+{^ These libraries are both meant to serve as examples of what can be done and
+   to also provide practical means for programming with fibers.  Hopefully there
+   will be many more libraries implemented in Picos like these providing
+   different approaches, patterns, and idioms for structuring concurrent
+   programs.}
diff --git a/lib/picos_stdio.fd/dune b/lib/picos_stdio.fd/dune
new file mode 100644
index 0000000..9a16f8a
--- /dev/null
+++ b/lib/picos_stdio.fd/dune
@@ -0,0 +1,6 @@
+(library
+ (name picos_stdio_fd)
+ (public_name picos_stdio.fd)
+ (libraries
+  (re_export unix)
+  picos_aux.rc))
diff --git a/lib/picos_fd/picos_fd.ml b/lib/picos_stdio.fd/picos_stdio_fd.ml
similarity index 95%
rename from lib/picos_fd/picos_fd.ml
rename to lib/picos_stdio.fd/picos_stdio_fd.ml
index 125327c..97231c4 100644
--- a/lib/picos_fd/picos_fd.ml
+++ b/lib/picos_stdio.fd/picos_stdio_fd.ml
@@ -11,7 +11,7 @@ module File_descr = struct
   let dispose = Unix.close
 end
 
-include Picos_rc.Make (File_descr) ()
+include Picos_aux_rc.Make (File_descr) ()
 
 let () =
   Stdlib.at_exit @@ fun () ->
diff --git a/lib/picos_fd/picos_fd.mli b/lib/picos_stdio.fd/picos_stdio_fd.mli
similarity index 78%
rename from lib/picos_fd/picos_fd.mli
rename to lib/picos_stdio.fd/picos_stdio_fd.mli
index 28504f5..f4508d1 100644
--- a/lib/picos_fd/picos_fd.mli
+++ b/lib/picos_stdio.fd/picos_stdio_fd.mli
@@ -4,4 +4,4 @@ val report_leaks : bool ref
 (** If [!report_leaks = true] then undisposed file descriptors will be reported
     {{!Stdlib.at_exit} at exit}. *)
 
-include Picos_rc.S with type Resource.t = Unix.file_descr
+include Picos_aux_rc.S with type Resource.t = Unix.file_descr
diff --git a/lib/picos_stdio.select/dune b/lib/picos_stdio.select/dune
new file mode 100644
index 0000000..b01cfec
--- /dev/null
+++ b/lib/picos_stdio.select/dune
@@ -0,0 +1,34 @@
+(library
+ (name picos_stdio_select)
+ (public_name picos_stdio.select)
+ (optional)
+ (libraries
+  (re_export picos)
+  (re_export picos_std.event)
+  (re_export picos_stdio.fd)
+  (re_export unix)
+  backoff
+  mtime
+  mtime.clock.os
+  multicore-magic
+  picos.domain
+  picos.thread
+  picos_aux.htbl
+  picos_stdio_thread_atomic
+  psq
+  threads.posix))
+
+(mdx
+ (package picos_meta)
+ (enabled_if
+  (>= %{ocaml_version} 5.1.0))
+ (libraries
+  picos
+  picos_mux.random
+  picos_std.event
+  picos_std.finally
+  picos_std.structured
+  picos_stdio
+  picos_stdio.select
+  unix)
+ (files picos_stdio_select.mli))
diff --git a/lib/picos_select/picos_select.ml b/lib/picos_stdio.select/picos_stdio_select.ml
similarity index 90%
rename from lib/picos_select/picos_select.ml
rename to lib/picos_stdio.select/picos_stdio_select.ml
index fac2aa8..3ceac82 100644
--- a/lib/picos_select/picos_select.ml
+++ b/lib/picos_stdio.select/picos_stdio_select.ml
@@ -1,5 +1,5 @@
 open Picos
-open Picos_sync
+open Picos_std_event
 
 let handle_sigchld_bit = 0b001
 let select_thread_running_on_main_domain_bit = 0b010
@@ -31,14 +31,17 @@ module RandomInt = struct
   let hash = Fun.id
 end
 
-let chld_awaiters = Picos_htbl.create ~hashed_type:(module RandomInt) ()
+module Htbl = Picos_aux_htbl
+
+let chld_awaiters = Htbl.create ~hashed_type:(module RandomInt) ()
 
 (* *)
 
 type cancel_at =
   | Cancel_at : {
       time : Mtime.span;
-      exn_bt : Exn_bt.t;
+      exn : exn;
+      bt : Printexc.raw_backtrace;
       computation : 'a Computation.t;
     }
       -> cancel_at
@@ -54,7 +57,7 @@ module Q =
 
 type return_on =
   | Return_on : {
-      file_descr : Picos_fd.t;
+      file_descr : Picos_stdio_fd.t;
       value : 'a;
       computation : 'a Computation.t;
       mutable alive : bool;
@@ -66,7 +69,7 @@ type phase = Continue | Select | Waking_up | Process
 type state = {
   phase : phase Atomic.t;
   mutable state : [ `Initial | `Starting | `Alive | `Stopping | `Stopped ];
-  mutable exn_bt : Exn_bt.t;
+  mutable exn_bt : exn * Printexc.raw_backtrace;
   mutable pipe_inn : Unix.file_descr;
   mutable pipe_out : Unix.file_descr;
   byte : Bytes.t;
@@ -94,7 +97,8 @@ type req = R : [< `Nothing | `Req ] tdt -> req [@@unboxed]
 type counter_state = { value : int; req : req }
 
 let intr_pending = Atomic.make { value = 0; req = R Nothing }
-let exit_exn_bt = Exn_bt.get_callstack 0 Exit
+let empty_bt = Printexc.get_callstack 0
+let exit_bt = (Exit, empty_bt)
 
 let cleared =
   let computation = Computation.create () in
@@ -108,7 +112,7 @@ let key =
   {
     phase = Atomic.make Continue;
     state = `Initial;
-    exn_bt = exit_exn_bt;
+    exn_bt = exit_bt;
     pipe_inn = Unix.stdin;
     pipe_out = Unix.stdin;
     byte = Bytes.create 1;
@@ -158,7 +162,7 @@ type fos = { n : int; unique_fds : Unix.file_descr list; ops : return_on list }
 
 let fos_empty = { n = 1; unique_fds = []; ops = [] }
 
-module Ht = Hashtbl.Make (Picos_fd.Resource)
+module Ht = Hashtbl.Make (Picos_stdio_fd.Resource)
 
 let rec process_fds ht unique_fds ops = function
   | [] ->
@@ -166,10 +170,10 @@ let rec process_fds ht unique_fds ops = function
       else { n = Ht.length ht; unique_fds; ops }
   | (Return_on r as op) :: ops_todo ->
       if Computation.is_running r.computation then begin
-        let file_descr = Picos_fd.unsafe_get r.file_descr in
+        let file_descr = Picos_stdio_fd.unsafe_get r.file_descr in
         match Ht.find ht file_descr with
         | `Return ->
-            Picos_fd.decr r.file_descr;
+            Picos_stdio_fd.decr r.file_descr;
             r.alive <- false;
             Computation.return r.computation r.value;
             process_fds ht unique_fds ops ops_todo
@@ -179,7 +183,7 @@ let rec process_fds ht unique_fds ops = function
             process_fds ht (file_descr :: unique_fds) (op :: ops) ops_todo
       end
       else begin
-        Picos_fd.decr r.file_descr;
+        Picos_stdio_fd.decr r.file_descr;
         process_fds ht unique_fds ops ops_todo
       end
 
@@ -199,13 +203,15 @@ let rec process_timeouts s =
       let elapsed = Mtime_clock.elapsed () in
       if Mtime.Span.compare e.time elapsed <= 0 then begin
         if Atomic.compare_and_set s.timeouts before after then
-          Computation.cancel e.computation e.exn_bt;
+          Computation.cancel e.computation e.exn e.bt;
         process_timeouts s
       end
       else
         Mtime.Span.to_float_ns (Mtime.Span.abs_diff e.time elapsed)
         *. (1. /. 1_000_000_000.)
 
+module Thread_atomic = Picos_stdio_thread_atomic
+
 let rec select_thread s timeout rd wr ex =
   if s.state == `Alive then begin
     let rd_fds, wr_fds, ex_fds =
@@ -225,9 +231,9 @@ let rec select_thread s timeout rd wr ex =
           let n = Unix.read s.pipe_inn s.byte 0 1 in
           assert (n = 1)
     end;
-    let rd = process_fds rd_fds rd (Picos_thread_atomic.exchange s.new_rd []) in
-    let wr = process_fds wr_fds wr (Picos_thread_atomic.exchange s.new_wr []) in
-    let ex = process_fds ex_fds ex (Picos_thread_atomic.exchange s.new_ex []) in
+    let rd = process_fds rd_fds rd (Thread_atomic.exchange s.new_rd []) in
+    let wr = process_fds wr_fds wr (Thread_atomic.exchange s.new_wr []) in
+    let ex = process_fds ex_fds ex (Thread_atomic.exchange s.new_ex []) in
     let timeout = process_timeouts s in
     let timeout =
       let state = Atomic.get intr_pending in
@@ -260,7 +266,9 @@ let select_thread s =
       s.pipe_out <- pipe_out;
       if try_transition s `Starting `Alive then
         select_thread s (-1.0) fos_empty fos_empty fos_empty
-    with exn -> s.exn_bt <- Exn_bt.get exn
+    with exn ->
+      let bt = Printexc.get_raw_backtrace () in
+      s.exn_bt <- (exn, bt)
   end;
   transition s `Stopped |> ignore;
   if s.pipe_inn != Unix.stdin then Unix.close s.pipe_inn;
@@ -282,7 +290,7 @@ let is_intr_sig signum = signum = config.intr_sig
 
 let handle_signal signal =
   if signal = Sys.sigchld then begin
-    Picos_htbl.remove_all chld_awaiters
+    Htbl.remove_all chld_awaiters
     |> Seq.iter @@ fun (_, Return r) ->
        r.alive <- false;
        Computation.return r.computation r.value
@@ -334,7 +342,8 @@ let[@inline never] init s =
         Picos_domain.at_exit @@ fun () ->
         if try_transition s `Alive `Stopping then wakeup s `Stopping;
         Thread.join thread;
-        if s.exn_bt != exit_exn_bt then Exn_bt.raise s.exn_bt
+        if s.exn_bt != exit_bt then
+          Printexc.raise_with_backtrace (fst s.exn_bt) (snd s.exn_bt)
     | exception exn ->
         transition s `Stopped |> ignore;
         raise exn
@@ -376,9 +385,9 @@ let to_deadline ~seconds =
   | None -> invalid_arg "seconds should be between 0 to pow(2, 53) nanoseconds"
   | Some span -> Mtime.Span.add (Mtime_clock.elapsed ()) span
 
-let[@alert "-handler"] cancel_after computation ~seconds exn_bt =
+let[@alert "-handler"] cancel_after computation ~seconds exn bt =
   let time = to_deadline ~seconds in
-  let entry = Cancel_at { time; exn_bt; computation } in
+  let entry = Cancel_at { time; exn; bt; computation } in
   let s = get () in
   let id = next_id s in
   add_timeout s id entry;
@@ -394,10 +403,10 @@ let[@alert "-handler"] timeout ~seconds =
     in
     let canceler =
       Trigger.from_action () inner @@ fun _ _ inner ->
-      Computation.cancel inner exit_exn_bt
+      Computation.cancel inner Exit empty_bt
     in
     if Computation.try_attach outer canceler then
-      cancel_after inner ~seconds exit_exn_bt
+      cancel_after inner ~seconds Exit empty_bt
   in
   Event.from_request { request }
 
@@ -408,18 +417,17 @@ let wakeup_action _trigger s (Return_on r) = if r.alive then wakeup s `Alive
 let[@alert "-handler"] rec insert_fd s fds (Return_on r as op) =
   let before = !fds in
   if Computation.is_running r.computation then
-    if Picos_thread_atomic.compare_and_set fds before (Return_on r :: before)
-    then
+    if Thread_atomic.compare_and_set fds before (Return_on r :: before) then
       let _ : bool =
         Computation.try_attach r.computation
           (Trigger.from_action s op wakeup_action)
       in
       wakeup s `Alive
     else insert_fd s fds op
-  else Picos_fd.decr r.file_descr
+  else Picos_stdio_fd.decr r.file_descr
 
 let return_on computation file_descr op value =
-  Picos_fd.incr file_descr;
+  Picos_stdio_fd.incr file_descr;
   let s = get () in
   insert_fd s
     (match op with `R -> s.new_rd | `W -> s.new_wr | `E -> s.new_ex)
@@ -430,7 +438,7 @@ let await_on file_descr op =
   return_on computation file_descr op file_descr;
   try Computation.await computation
   with exn ->
-    Computation.cancel computation exit_exn_bt;
+    Computation.cancel computation Exit empty_bt;
     raise exn
 
 let on file_descr op =
@@ -493,7 +501,7 @@ module Intr = struct
       let computation = Computation.with_action req id intr_action in
       r.computation <- computation;
       Picos_thread.TLS.set intr_key req;
-      let entry = Cancel_at { time; exn_bt = exit_exn_bt; computation } in
+      let entry = Cancel_at { time; exn = Exit; bt = empty_bt; computation } in
       add_timeout state id entry;
       let was_blocked : int list =
         Thread.sigmask SIG_UNBLOCK config.intr_sigs
@@ -529,7 +537,7 @@ end
 
 let rec insert return =
   let id = Random.bits () in
-  if Picos_htbl.try_add chld_awaiters id return then id else insert return
+  if Htbl.try_add chld_awaiters id return then id else insert return
 
 let[@alert "-handler"] return_on_sigchld computation value =
   if
@@ -548,7 +556,7 @@ let[@alert "-handler"] return_on_sigchld computation value =
       (* It should be extremely rare, but possible, that the return was already
          removed and another added just at this point and so we must account for
          the possibility and make sure that whatever we remove is completed. *)
-      match Picos_htbl.remove_exn chld_awaiters id with
+      match Htbl.remove_exn chld_awaiters id with
       | Return that_r as that ->
           if this != that then
             Computation.return that_r.computation that_r.value
diff --git a/lib/picos_select/picos_select.mli b/lib/picos_stdio.select/picos_stdio_select.mli
similarity index 84%
rename from lib/picos_select/picos_select.mli
rename to lib/picos_stdio.select/picos_stdio_select.mli
index ea9daab..1703eb5 100644
--- a/lib/picos_select/picos_select.mli
+++ b/lib/picos_stdio.select/picos_stdio_select.mli
@@ -12,29 +12,31 @@
     ⚠️ All the usual limitations of the {!Unix} module apply. *)
 
 open Picos
-open Picos_sync
+open Picos_std_event
 
 (** {1 API} *)
 
 (** {2 Timeouts} *)
 
-val cancel_after : _ Computation.t -> seconds:float -> Exn_bt.t -> unit
-(** [cancel_after computation ~seconds exn_bt] arranges for [computation] to be
-    {{!Picos.Computation.cancel} canceled} with given [exn_bt] after given time
-    in [seconds].  Completion of the [computation] before the specified time
-    effectively cancels the timeout.
+val cancel_after :
+  _ Computation.t -> seconds:float -> exn -> Printexc.raw_backtrace -> unit
+(** [cancel_after computation ~seconds exn bt] arranges for [computation] to be
+    {{!Picos.Computation.cancel} canceled} with given exception and backtrace
+    after given time in [seconds].  Completion of the [computation] before the
+    specified time effectively cancels the timeout.
 
     ℹ️ You can use [cancel_after] to implement the handler for the
     {{!Picos.Computation.Cancel_after} [Cancel_after]} effect. *)
 
 val timeout : seconds:float -> unit Event.t
-(** [timeout ~seconds] returns an {{!Picos_sync.Event} event} that, on each time
-    after being {{!Picos_sync.Event.sync} synchronized} on, can be committed to
-    after the specified number of [seconds]. *)
+(** [timeout ~seconds] returns an {{!Picos_std_event.Event} event} that, on each
+    time after being {{!Picos_std_event.Event.sync} synchronized} on, can be
+    committed to after the specified number of [seconds]. *)
 
 (** {2 IO} *)
 
-val return_on : 'a Computation.t -> Picos_fd.t -> [ `R | `W | `E ] -> 'a -> unit
+val return_on :
+  'a Computation.t -> Picos_stdio_fd.t -> [ `R | `W | `E ] -> 'a -> unit
 (** [return_on computation fd op value] arranges for [computation] to be
     {{!Picos.Computation.return} returned} with given [value] when [fd] becomes
     available for [op].  Completion of the [computation] before the [fd] becomes
@@ -43,12 +45,12 @@ val return_on : 'a Computation.t -> Picos_fd.t -> [ `R | `W | `E ] -> 'a -> unit
     ℹ️ Using {!Unix.set_nonblock} and [return_on] you can implement direct-style
     transparently asynchronous IO on top of the {!Unix} module. *)
 
-val await_on : Picos_fd.t -> [ `R | `W | `E ] -> Picos_fd.t
+val await_on : Picos_stdio_fd.t -> [ `R | `W | `E ] -> Picos_stdio_fd.t
 (** [await_on fd op] awaits until [fd] becomes available for [op]. *)
 
-val on : Picos_fd.t -> [ `R | `W | `E ] -> unit Event.t
-(** [on fd op] returns an {{!Picos_sync.Event} event} that can be committed to
-    when [fd] becomes available for [op]. *)
+val on : Picos_stdio_fd.t -> [ `R | `W | `E ] -> unit Event.t
+(** [on fd op] returns an {{!Picos_std_event.Event} event} that can be committed
+    to when [fd] becomes available for [op]. *)
 
 module Intr : sig
   (** A mechanism to interrupt blocking {!Unix} IO operations.
@@ -94,7 +96,7 @@ val return_on_sigchld : 'a Computation.t -> 'a -> unit
     other purposes. *)
 
 val on_sigchld : unit Event.t
-(** [on_sigchld] is an {{!Picos_sync.Event} event} that can be committed to
+(** [on_sigchld] is an {{!Picos_std_event.Event} event} that can be committed to
     after a {!Sys.sigchld} signal has occurred. *)
 
 (** {2 Configuration} *)
@@ -147,10 +149,10 @@ val handle_signal : int -> unit
 
     {[
       open Picos
-      open Picos_finally
-      open Picos_structured
+      open Picos_std_event
+      open Picos_std_finally
+      open Picos_std_structured
       open Picos_stdio
-      open Picos_sync
     ]}
 
     {2 One of many}
@@ -158,7 +160,7 @@ val handle_signal : int -> unit
     Here is an example that awaits for one of multiple alternative events:
 
     {[
-      # Picos_randos.run_on ~n_domains:2 @@ fun () ->
+      # Picos_mux_random.run_on ~n_domains:2 @@ fun () ->
 
         let@ msg_inn1, msg_out1 =
           finally Unix.close_pair @@ fun () ->
@@ -199,19 +201,19 @@ val handle_signal : int -> unit
           Flock.fork begin fun () ->
             while true do
               Event.select [
-                Picos_select.on msg_inn1 `R
+                Picos_stdio_select.on msg_inn1 `R
                   |> Event.map begin fun () ->
                     print_endline "Inn1";
                     read1 msg_inn1;
                     write1 syn_out
                   end;
-                Picos_select.on msg_inn2 `R
+                Picos_stdio_select.on msg_inn2 `R
                   |> Event.map begin fun () ->
                     print_endline "Inn2";
                     read1 msg_inn2;
                     write1 syn_out;
                   end;
-                Picos_select.timeout
+                Picos_stdio_select.timeout
                     ~seconds:60.0
                   |> Event.map begin fun () ->
                     print_endline "Timeout";
@@ -231,5 +233,5 @@ val handle_signal : int -> unit
       - : unit = ()
     ]}
 
-    Above we use the {{!Picos_sync.Event} [Event]} module providing composable
-    events. *)
+    Above we use the {{!Picos_std_event.Event} [Event]} module providing
+    composable events. *)
diff --git a/lib/picos_stdio.thread_atomic/dune b/lib/picos_stdio.thread_atomic/dune
new file mode 100644
index 0000000..1279bb6
--- /dev/null
+++ b/lib/picos_stdio.thread_atomic/dune
@@ -0,0 +1,3 @@
+(library
+ (name picos_stdio_thread_atomic)
+ (package picos_stdio))
diff --git a/lib/picos_thread_atomic/picos_thread_atomic.ml b/lib/picos_stdio.thread_atomic/picos_stdio_thread_atomic.ml
similarity index 100%
rename from lib/picos_thread_atomic/picos_thread_atomic.ml
rename to lib/picos_stdio.thread_atomic/picos_stdio_thread_atomic.ml
diff --git a/lib/picos_thread_atomic/picos_thread_atomic.mli b/lib/picos_stdio.thread_atomic/picos_stdio_thread_atomic.mli
similarity index 100%
rename from lib/picos_thread_atomic/picos_thread_atomic.mli
rename to lib/picos_stdio.thread_atomic/picos_stdio_thread_atomic.mli
diff --git a/lib/picos_stdio/dune b/lib/picos_stdio/dune
index 7160ded..848f8ea 100644
--- a/lib/picos_stdio/dune
+++ b/lib/picos_stdio/dune
@@ -1,22 +1,27 @@
 (library
  (name picos_stdio)
- (public_name picos.stdio)
+ (public_name picos_stdio)
  (root_module Deps)
  (libraries
   (re_export unix)
-  (re_export picos.fd)
-  picos.htbl
-  picos.select))
+  (re_export picos_stdio_fd)
+  (re_export picos)
+  picos_aux.htbl
+  picos_stdio.select))
 
 (mdx
+ (package picos_meta)
  (enabled_if
   (>= %{ocaml_version} 5.1.0))
  (libraries
   picos
-  picos.exn_bt
-  picos.finally
-  picos.randos
-  picos.stdio
-  picos.structured
+  picos_mux.random
+  picos_std.finally
+  picos_std.structured
+  picos_stdio
   unix)
  (files picos_stdio.mli))
+
+(documentation
+ (package picos_stdio)
+ (mld_files index))
diff --git a/lib/picos_stdio/index.mld b/lib/picos_stdio/index.mld
new file mode 100644
index 0000000..e6728fc
--- /dev/null
+++ b/lib/picos_stdio/index.mld
@@ -0,0 +1,13 @@
+{0 Asynchronous IO system for Picos}
+
+This package provides an asynchronous IO system for {!Picos}.
+
+{!modules:
+  Picos_stdio
+  Picos_stdio_select
+  Picos_stdio_fd
+}
+
+{^ The IO libraries in this package are currently built only on top of the
+   standard libraries distributed with OCaml and are hopefully useful for
+   building simple applications.}
diff --git a/lib/picos_stdio/picos_stdio.ml b/lib/picos_stdio/picos_stdio.ml
index 61e044b..3e84af7 100644
--- a/lib/picos_stdio/picos_stdio.ml
+++ b/lib/picos_stdio/picos_stdio.ml
@@ -1,11 +1,14 @@
 open Picos
+module Select = Picos_stdio_select
+module Fd = Picos_stdio_fd
+module Htbl = Picos_aux_htbl
 
-let nonblock_fds = Picos_htbl.create ~hashed_type:(module Picos_fd.Resource) ()
+let nonblock_fds = Htbl.create ~hashed_type:(module Fd.Resource) ()
 
 module Unix = struct
   include Unix
 
-  type file_descr = Picos_fd.t
+  type file_descr = Fd.t
 
   let is_oob flag = MSG_OOB == flag
   let is_nonblock flag = O_NONBLOCK == flag
@@ -20,73 +23,73 @@ module Unix = struct
      block, so we do the same thing as with [EAGAIN]. *)
 
   let[@inline] intr_req fd =
-    if Sys.win32 || Picos_htbl.mem nonblock_fds (Picos_fd.unsafe_get fd) then
-      Picos_select.Intr.nothing
-    else Picos_select.Intr.req ~seconds:0.000_01 (* 10μs - TODO *)
+    if Sys.win32 || Htbl.mem nonblock_fds (Fd.unsafe_get fd) then
+      Select.Intr.nothing
+    else Select.Intr.req ~seconds:0.000_01 (* 10μs - TODO *)
 
   let rec again_0 fd fn op =
     let intr = intr_req fd in
-    match fn (Picos_fd.unsafe_get fd) with
+    match fn (Fd.unsafe_get fd) with
     | result ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         result
     | exception Unix.Unix_error ((EAGAIN | EINTR | EWOULDBLOCK), _, _) ->
-        Picos_select.Intr.clr intr;
-        again_0 (Picos_select.await_on fd op) fn op
+        Select.Intr.clr intr;
+        again_0 (Select.await_on fd op) fn op
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
   let rec again_cloexec_0 ?cloexec fd fn op =
     let intr = intr_req fd in
-    match fn ?cloexec (Picos_fd.unsafe_get fd) with
+    match fn ?cloexec (Fd.unsafe_get fd) with
     | result ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         result
     | exception Unix.Unix_error ((EAGAIN | EINTR | EWOULDBLOCK), _, _) ->
-        Picos_select.Intr.clr intr;
-        again_cloexec_0 ?cloexec (Picos_select.await_on fd op) fn op
+        Select.Intr.clr intr;
+        again_cloexec_0 ?cloexec (Select.await_on fd op) fn op
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
   let rec again_3 fd x1 x2 x3 fn op =
     let intr = intr_req fd in
-    match fn (Picos_fd.unsafe_get fd) x1 x2 x3 with
+    match fn (Fd.unsafe_get fd) x1 x2 x3 with
     | result ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         result
     | exception Unix.Unix_error ((EAGAIN | EINTR | EWOULDBLOCK), _, _) ->
-        Picos_select.Intr.clr intr;
-        again_3 (Picos_select.await_on fd op) x1 x2 x3 fn op
+        Select.Intr.clr intr;
+        again_3 (Select.await_on fd op) x1 x2 x3 fn op
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
   let rec again_4 fd x1 x2 x3 x4 fn op =
     let intr = intr_req fd in
-    match fn (Picos_fd.unsafe_get fd) x1 x2 x3 x4 with
+    match fn (Fd.unsafe_get fd) x1 x2 x3 x4 with
     | result ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         result
     | exception Unix.Unix_error ((EAGAIN | EINTR | EWOULDBLOCK), _, _) ->
-        Picos_select.Intr.clr intr;
-        again_4 (Picos_select.await_on fd op) x1 x2 x3 x4 fn op
+        Select.Intr.clr intr;
+        again_4 (Select.await_on fd op) x1 x2 x3 x4 fn op
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
   let rec again_5 fd x1 x2 x3 x4 x5 fn op =
     let intr = intr_req fd in
-    match fn (Picos_fd.unsafe_get fd) x1 x2 x3 x4 x5 with
+    match fn (Fd.unsafe_get fd) x1 x2 x3 x4 x5 with
     | result ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         result
     | exception Unix.Unix_error ((EAGAIN | EINTR | EWOULDBLOCK), _, _) ->
-        Picos_select.Intr.clr intr;
-        again_5 (Picos_select.await_on fd op) x1 x2 x3 x4 x5 fn op
+        Select.Intr.clr intr;
+        again_5 (Select.await_on fd op) x1 x2 x3 x4 x5 fn op
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
   (* [EINPROGRESS] indicates that a socket operation is being performed
@@ -95,8 +98,8 @@ module Unix = struct
 
   let progress_1 fd x1 fn op name =
     let intr = intr_req fd in
-    match fn (Picos_fd.unsafe_get fd) x1 with
-    | () -> Picos_select.Intr.clr intr
+    match fn (Fd.unsafe_get fd) x1 with
+    | () -> Select.Intr.clr intr
     | exception
         Unix.Unix_error ((EAGAIN | EINPROGRESS | EINTR | EWOULDBLOCK), _, _) ->
       begin
@@ -109,26 +112,26 @@ module Unix = struct
 
            For [connect] both [EINPROGRESS] and [EINTR] mean that connection
            will be established asynchronously and we use [select] to wait. *)
-        Picos_select.Intr.clr intr;
-        let fd = Picos_select.await_on fd op in
-        match Unix.getsockopt_error (Picos_fd.unsafe_get fd) with
+        Select.Intr.clr intr;
+        let fd = Select.await_on fd op in
+        match Unix.getsockopt_error (Fd.unsafe_get fd) with
         | None -> ()
         | Some error -> raise (Unix.Unix_error (error, name, ""))
       end
     | exception exn ->
-        Picos_select.Intr.clr intr;
+        Select.Intr.clr intr;
         raise exn
 
-  let stdin = Picos_fd.create ~dispose:false Unix.stdin
-  and stdout = Picos_fd.create ~dispose:false Unix.stdout
-  and stderr = Picos_fd.create ~dispose:false Unix.stderr
+  let stdin = Fd.create ~dispose:false Unix.stdin
+  and stdout = Fd.create ~dispose:false Unix.stdout
+  and stderr = Fd.create ~dispose:false Unix.stderr
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html *)
   let openfile path flags file_perm =
-    let fd = Picos_fd.create (Unix.openfile path flags file_perm) in
+    let fd = Fd.create (Unix.openfile path flags file_perm) in
     if List.exists is_nonblock flags then begin
       let if_not_added_fd_has_been_closed_outside =
-        Picos_htbl.try_add nonblock_fds (Picos_fd.unsafe_get fd) ()
+        Htbl.try_add nonblock_fds (Fd.unsafe_get fd) ()
       in
       assert if_not_added_fd_has_been_closed_outside
     end;
@@ -136,10 +139,8 @@ module Unix = struct
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html *)
   let close fd =
-    let _ : bool =
-      Picos_htbl.try_remove nonblock_fds (Picos_fd.unsafe_get fd)
-    in
-    Picos_fd.decr ~close:true fd
+    let _ : bool = Htbl.try_remove nonblock_fds (Fd.unsafe_get fd) in
+    Fd.decr ~close:true fd
 
   let close_pair (fd1, fd2) =
     close fd1;
@@ -174,18 +175,18 @@ module Unix = struct
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html *)
   let lseek fd amount seek_command =
-    Unix.lseek (Picos_fd.unsafe_get fd) amount seek_command
+    Unix.lseek (Fd.unsafe_get fd) amount seek_command
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html *)
-  let ftruncate fd size = Unix.ftruncate (Picos_fd.unsafe_get fd) size
+  let ftruncate fd size = Unix.ftruncate (Fd.unsafe_get fd) size
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html *)
-  let fstat fd = Unix.fstat (Picos_fd.unsafe_get fd)
+  let fstat fd = Unix.fstat (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html *)
-  let isatty fd = Unix.isatty (Picos_fd.unsafe_get fd)
+  let isatty fd = Unix.isatty (Fd.unsafe_get fd)
 
   (* *)
 
@@ -194,14 +195,13 @@ module Unix = struct
 
     (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html *)
     let lseek fd amount seek_command =
-      Unix.LargeFile.lseek (Picos_fd.unsafe_get fd) amount seek_command
+      Unix.LargeFile.lseek (Fd.unsafe_get fd) amount seek_command
 
     (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html *)
-    let ftruncate fd size =
-      Unix.LargeFile.ftruncate (Picos_fd.unsafe_get fd) size
+    let ftruncate fd size = Unix.LargeFile.ftruncate (Fd.unsafe_get fd) size
 
     (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html *)
-    let fstat fd = Unix.LargeFile.fstat (Picos_fd.unsafe_get fd)
+    let fstat fd = Unix.LargeFile.fstat (Fd.unsafe_get fd)
   end
 
   (* *)
@@ -210,60 +210,55 @@ module Unix = struct
 
      This can raise EAGAIN, but it probably should not be handled? *)
   let map_file fd ?pos kind layout shared dims =
-    Unix.map_file (Picos_fd.unsafe_get fd) ?pos kind layout shared dims
+    Unix.map_file (Fd.unsafe_get fd) ?pos kind layout shared dims
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html *)
-  let fchmod fd file_perm = Unix.fchmod (Picos_fd.unsafe_get fd) file_perm
+  let fchmod fd file_perm = Unix.fchmod (Fd.unsafe_get fd) file_perm
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html *)
-  let fchown fd uid gid = Unix.fchown (Picos_fd.unsafe_get fd) uid gid
+  let fchown fd uid gid = Unix.fchown (Fd.unsafe_get fd) uid gid
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html *)
-  let dup ?cloexec fd =
-    Picos_fd.create (Unix.dup ?cloexec (Picos_fd.unsafe_get fd))
+  let dup ?cloexec fd = Fd.create (Unix.dup ?cloexec (Fd.unsafe_get fd))
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html *)
   let dup2 ?cloexec src dst =
-    Unix.dup2 ?cloexec (Picos_fd.unsafe_get src) (Picos_fd.unsafe_get dst)
+    Unix.dup2 ?cloexec (Fd.unsafe_get src) (Fd.unsafe_get dst)
 
   let set_nonblock fd =
-    Unix.set_nonblock (Picos_fd.unsafe_get fd);
-    Picos_htbl.try_add nonblock_fds (Picos_fd.unsafe_get fd) () |> ignore
+    Unix.set_nonblock (Fd.unsafe_get fd);
+    Htbl.try_add nonblock_fds (Fd.unsafe_get fd) () |> ignore
 
   let clear_nonblock fd =
-    Unix.clear_nonblock (Picos_fd.unsafe_get fd);
-    Picos_htbl.try_remove nonblock_fds (Picos_fd.unsafe_get fd) |> ignore
+    Unix.clear_nonblock (Fd.unsafe_get fd);
+    Htbl.try_remove nonblock_fds (Fd.unsafe_get fd) |> ignore
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html *)
-  let set_close_on_exec fd = Unix.set_close_on_exec (Picos_fd.unsafe_get fd)
+  let set_close_on_exec fd = Unix.set_close_on_exec (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html *)
-  let clear_close_on_exec fd = Unix.clear_close_on_exec (Picos_fd.unsafe_get fd)
+  let clear_close_on_exec fd = Unix.clear_close_on_exec (Fd.unsafe_get fd)
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html *)
   let pipe ?cloexec () =
     let inn, out = Unix.pipe ?cloexec () in
-    (Picos_fd.create inn, Picos_fd.create out)
+    (Fd.create inn, Fd.create out)
 
   (* *)
 
   let create_process prog args stdin stdout stderr =
-    Unix.create_process prog args
-      (Picos_fd.unsafe_get stdin)
-      (Picos_fd.unsafe_get stdout)
-      (Picos_fd.unsafe_get stderr)
+    Unix.create_process prog args (Fd.unsafe_get stdin) (Fd.unsafe_get stdout)
+      (Fd.unsafe_get stderr)
 
   let create_process_env prog args env stdin stdout stderr =
-    Unix.create_process_env prog args env
-      (Picos_fd.unsafe_get stdin)
-      (Picos_fd.unsafe_get stdout)
-      (Picos_fd.unsafe_get stderr)
+    Unix.create_process_env prog args env (Fd.unsafe_get stdin)
+      (Fd.unsafe_get stdout) (Fd.unsafe_get stderr)
 
   (*let open_process_in _ = failwith "TODO"*)
   (*let open_process_out _ = failwith "TODO"*)
@@ -309,7 +304,7 @@ module Unix = struct
       Unix.waitpid (Wait_flag.to_flags bits) pid
     else
       let computation = Computation.create ~mode:`LIFO () in
-      Picos_select.return_on_sigchld computation ();
+      Select.return_on_sigchld computation ();
       match
         Unix.waitpid (Wait_flag.to_flags (bits lor Wait_flag.nohang_bit)) pid
       with
@@ -378,18 +373,18 @@ module Unix = struct
 
   exception Done
 
-  let done_bt = Exn_bt.get_callstack 0 Done
+  let empty_bt = Printexc.get_callstack 0
 
   let[@alert "-handler"] select rds wrs exs seconds =
     let overall = Computation.create ~mode:`LIFO () in
     let canceler =
       Trigger.from_action overall () @@ fun _ overall _ ->
-      Picos_select.cancel_after overall ~seconds:0.0 done_bt
+      Select.cancel_after overall ~seconds:0.0 Done empty_bt
     in
     let prepare op fd =
       let computation = Computation.create ~mode:`LIFO () in
       if Computation.try_attach computation canceler then
-        Picos_select.return_on computation fd op true;
+        Select.return_on computation fd op true;
       computation
     in
     let rdcs = List.map (prepare `R) rds in
@@ -405,7 +400,7 @@ module Unix = struct
     if not (Computation.try_attach overall finisher) then
       Trigger.signal finisher
     else if 0.0 <= seconds then
-      Picos_select.cancel_after overall ~seconds done_bt;
+      Select.cancel_after overall ~seconds Done empty_bt;
     match Computation.await overall with
     | () -> assert false
     | exception Done ->
@@ -420,30 +415,30 @@ module Unix = struct
           zip_filter Computation.await wrs wrcs,
           zip_filter Computation.await exs excs )
     | exception cancelation_exn ->
-        Computation.cancel overall done_bt;
+        Computation.cancel overall Done empty_bt;
         raise cancelation_exn
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/lockf.html *)
   let lockf fd lock_command length =
-    Unix.lockf (Picos_fd.unsafe_get fd) lock_command length
+    Unix.lockf (Fd.unsafe_get fd) lock_command length
 
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html *)
   let socket ?cloexec socket_domain socket_type protocol =
-    Picos_fd.create (Unix.socket ?cloexec socket_domain socket_type protocol)
+    Fd.create (Unix.socket ?cloexec socket_domain socket_type protocol)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html *)
   let socketpair ?cloexec socket_domain socket_type mystery =
     let fst, snd = Unix.socketpair ?cloexec socket_domain socket_type mystery in
-    (Picos_fd.create fst, Picos_fd.create snd)
+    (Fd.create fst, Fd.create snd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html *)
   let accept ?cloexec fd =
     let fd, sockaddr = again_cloexec_0 ?cloexec fd Unix.accept `R in
-    (Picos_fd.create fd, sockaddr)
+    (Fd.create fd, sockaddr)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html *)
   let bind fd sockaddr = progress_1 fd sockaddr Unix.bind `W "bind"
@@ -452,17 +447,17 @@ module Unix = struct
   let connect fd sockaddr = progress_1 fd sockaddr Unix.connect `W "connect"
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html *)
-  let listen fd max_pending = Unix.listen (Picos_fd.unsafe_get fd) max_pending
+  let listen fd max_pending = Unix.listen (Fd.unsafe_get fd) max_pending
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html *)
   let shutdown fd shutdown_command =
-    Unix.shutdown (Picos_fd.unsafe_get fd) shutdown_command
+    Unix.shutdown (Fd.unsafe_get fd) shutdown_command
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html *)
-  let getsockname fd = Unix.getsockname (Picos_fd.unsafe_get fd)
+  let getsockname fd = Unix.getsockname (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html *)
-  let getpeername fd = Unix.getpeername (Picos_fd.unsafe_get fd)
+  let getpeername fd = Unix.getpeername (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html *)
   let recv fd bytes offset length flags =
@@ -493,32 +488,30 @@ module Unix = struct
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html *)
-  let getsockopt fd option = Unix.getsockopt (Picos_fd.unsafe_get fd) option
+  let getsockopt fd option = Unix.getsockopt (Fd.unsafe_get fd) option
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html *)
-  let setsockopt fd option bool =
-    Unix.setsockopt (Picos_fd.unsafe_get fd) option bool
+  let setsockopt fd option bool = Unix.setsockopt (Fd.unsafe_get fd) option bool
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html *)
-  let getsockopt_int fd option =
-    Unix.getsockopt_int (Picos_fd.unsafe_get fd) option
+  let getsockopt_int fd option = Unix.getsockopt_int (Fd.unsafe_get fd) option
 
   let setsockopt_int fd option int =
-    Unix.setsockopt_int (Picos_fd.unsafe_get fd) option int
+    Unix.setsockopt_int (Fd.unsafe_get fd) option int
 
   let getsockopt_optint fd option =
-    Unix.getsockopt_optint (Picos_fd.unsafe_get fd) option
+    Unix.getsockopt_optint (Fd.unsafe_get fd) option
 
   let setsockopt_optint fd option optint =
-    Unix.setsockopt_optint (Picos_fd.unsafe_get fd) option optint
+    Unix.setsockopt_optint (Fd.unsafe_get fd) option optint
 
   let getsockopt_float fd option =
-    Unix.getsockopt_float (Picos_fd.unsafe_get fd) option
+    Unix.getsockopt_float (Fd.unsafe_get fd) option
 
   let setsockopt_float fd option float =
-    Unix.setsockopt_float (Picos_fd.unsafe_get fd) option float
+    Unix.setsockopt_float (Fd.unsafe_get fd) option float
 
-  let getsockopt_error fd = Unix.getsockopt_error (Picos_fd.unsafe_get fd)
+  let getsockopt_error fd = Unix.getsockopt_error (Fd.unsafe_get fd)
 
   (* *)
 
@@ -529,22 +522,21 @@ module Unix = struct
   (* *)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html *)
-  let tcgetattr fd = Unix.tcgetattr (Picos_fd.unsafe_get fd)
+  let tcgetattr fd = Unix.tcgetattr (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html *)
   let tcsetattr fd setattr_when terminal_io =
-    Unix.tcsetattr (Picos_fd.unsafe_get fd) setattr_when terminal_io
+    Unix.tcsetattr (Fd.unsafe_get fd) setattr_when terminal_io
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html *)
-  let tcsendbreak fd duration =
-    Unix.tcsendbreak (Picos_fd.unsafe_get fd) duration
+  let tcsendbreak fd duration = Unix.tcsendbreak (Fd.unsafe_get fd) duration
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html *)
-  let tcdrain fd = Unix.tcdrain (Picos_fd.unsafe_get fd)
+  let tcdrain fd = Unix.tcdrain (Fd.unsafe_get fd)
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html *)
-  let tcflush fd flush_queue = Unix.tcflush (Picos_fd.unsafe_get fd) flush_queue
+  let tcflush fd flush_queue = Unix.tcflush (Fd.unsafe_get fd) flush_queue
 
   (* https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html *)
-  let tcflow fd flow_action = Unix.tcflow (Picos_fd.unsafe_get fd) flow_action
+  let tcflow fd flow_action = Unix.tcflow (Fd.unsafe_get fd) flow_action
 end
diff --git a/lib/picos_stdio/picos_stdio.mli b/lib/picos_stdio/picos_stdio.mli
index 5bc7dcf..ee367fe 100644
--- a/lib/picos_stdio/picos_stdio.mli
+++ b/lib/picos_stdio/picos_stdio.mli
@@ -38,13 +38,13 @@ module Unix : sig
       Windows, scheduler friendly blocking only works properly with non-blocking
       file descriptors, i.e. sockets.
 
-      ⚠️ This module uses {!Picos_select} and you may need to
-      {{!Picos_select.configure} configure} it at start of your application.
+      ⚠️ This module uses {!Picos_stdio_select} and you may need to
+      {{!Picos_stdio_select.configure} configure} it at start of your application.
 
       Please consult the documentation of the {{!Deps.Unix} [Unix]} module that
       comes with OCaml. *)
 
-  type file_descr = Picos_fd.t
+  type file_descr = Picos_stdio_fd.t
   (** Opaque type alias for {{!Deps.Unix.file_descr} [Unix.file_descr]}.
 
       ⚠️ Please consider the reference counting of file descriptors as an
@@ -350,10 +350,10 @@ module Unix : sig
     float ->
     file_descr list * file_descr list * file_descr list
   (** [select rds wrs exs timeout] is like {!Deps.Unix.select}, but uses
-      {!Picos_select} to avoid blocking the thread.
+      {!Picos_stdio_select} to avoid blocking the thread.
 
       🐌 You may find composing multi file descriptor awaits via other means
-      with {!Picos_select} more flexible and efficient. *)
+      with {!Picos_stdio_select} more flexible and efficient. *)
 
   type lock_command = Unix.lock_command =
     | F_ULOCK
@@ -689,8 +689,8 @@ end
     First we open some modules for convenience:
 
     {[
-      open Picos_finally
-      open Picos_structured
+      open Picos_std_finally
+      open Picos_std_structured
       open Picos_stdio
     ]}
 
@@ -700,7 +700,7 @@ end
     pipes:
 
     {[
-      # Picos_randos.run_on ~n_domains:2 @@ fun () ->
+      # Picos_mux_random.run_on ~n_domains:2 @@ fun () ->
 
         let@ msg_i, msg_o =
           finally Unix.close_pair @@ fun () ->
diff --git a/lib/picos_stdio_cohttp/dune b/lib/picos_stdio_cohttp/dune
index 6a74c42..5f110c5 100644
--- a/lib/picos_stdio_cohttp/dune
+++ b/lib/picos_stdio_cohttp/dune
@@ -1,15 +1,16 @@
 (library
  (name picos_stdio_cohttp)
- (public_name picos.stdio.cohttp)
+ (public_name picos_stdio_cohttp)
  (libraries
-  (re_export picos.stdio)
+  (re_export picos_stdio)
   (re_export cohttp)
   (re_export uri)
   fmt
-  picos.finally
-  picos.structured))
+  picos_std.finally
+  picos_std.structured))
 
 (mdx
+ (package picos_meta)
  (enabled_if
   (and
    (>= %{ocaml_version} 5.1.0)
@@ -17,10 +18,14 @@
  (libraries
   cohttp
   http
-  picos.finally
-  picos.randos
-  picos.stdio
-  picos.stdio.cohttp
-  picos.structured
+  picos_mux.random
+  picos_std.finally
+  picos_std.structured
+  picos_stdio
+  picos_stdio_cohttp
   uri)
  (files picos_stdio_cohttp.mli))
+
+(documentation
+ (package picos_stdio_cohttp)
+ (mld_files index))
diff --git a/lib/picos_stdio_cohttp/index.mld b/lib/picos_stdio_cohttp/index.mld
new file mode 100644
index 0000000..8e92b4a
--- /dev/null
+++ b/lib/picos_stdio_cohttp/index.mld
@@ -0,0 +1,8 @@
+{0 Cohttp running on Picos stdio}
+
+This package provides a minimalistic {{:https://github.com/mirage/ocaml-cohttp/}
+Cohttp} implementation for {!Picos} using {!Picos_stdio}.
+
+{!modules:
+  Picos_stdio_cohttp
+}
\ No newline at end of file
diff --git a/lib/picos_stdio_cohttp/picos_stdio_cohttp.ml b/lib/picos_stdio_cohttp/picos_stdio_cohttp.ml
index 1d692be..9e5361e 100644
--- a/lib/picos_stdio_cohttp/picos_stdio_cohttp.ml
+++ b/lib/picos_stdio_cohttp/picos_stdio_cohttp.ml
@@ -1,6 +1,6 @@
-open Picos_finally
+open Picos_std_finally
+open Picos_std_structured
 open Picos_stdio
-open Picos_structured
 
 (* *)
 
diff --git a/lib/picos_stdio_cohttp/picos_stdio_cohttp.mli b/lib/picos_stdio_cohttp/picos_stdio_cohttp.mli
index 6e5e717..6f7f9fc 100644
--- a/lib/picos_stdio_cohttp/picos_stdio_cohttp.mli
+++ b/lib/picos_stdio_cohttp/picos_stdio_cohttp.mli
@@ -42,10 +42,10 @@ end
 
     {[
       open Cohttp
-      open Picos_finally
+      open Picos_std_finally
+      open Picos_std_structured
       open Picos_stdio
       open Picos_stdio_cohttp
-      open Picos_structured
     ]}
 
     {2 A server and client}
@@ -106,7 +106,7 @@ end
     Now we are ready to put everything together:
 
     {[
-      # Picos_randos.run_on ~n_domains:2 @@ fun () ->
+      # Picos_mux_random.run_on ~n_domains:2 @@ fun () ->
 
         let@ server_socket =
           finally Unix.close @@ fun () ->
@@ -141,6 +141,6 @@ end
     We first create the [server_socket] and obtain the [server_port] and
     ultimately the [server_uri] from it — typically one can avoid this
     complexity and use a fixed port.  We then create a
-    {{!Picos_structured.Flock} flock} for running the server as a concurrent
+    {{!Picos_std_structured.Flock} flock} for running the server as a concurrent
     fiber, which we arrange to terminate at the end of the scope.  Finally we
     act as the client to get a greeting from the server. *)
diff --git a/lib/picos_structured/dune b/lib/picos_structured/dune
deleted file mode 100644
index dca0cf2..0000000
--- a/lib/picos_structured/dune
+++ /dev/null
@@ -1,24 +0,0 @@
-(library
- (name picos_structured)
- (public_name picos.structured)
- (libraries
-  (re_export picos.sync)
-  picos
-  backoff
-  multicore-magic))
-
-(mdx
- (enabled_if
-  (and
-   (>= %{ocaml_version} 5.0.0)
-   (= %{env:OPAM_REPO_CI=false} false)))
- (libraries
-  picos.exn_bt
-  picos.fifos
-  picos.finally
-  picos.randos
-  picos.stdio
-  picos.structured
-  picos.sync
-  unix)
- (files picos_structured.mli))
diff --git a/lib/picos_sync/dune b/lib/picos_sync/dune
deleted file mode 100644
index 79ab72f..0000000
--- a/lib/picos_sync/dune
+++ /dev/null
@@ -1,10 +0,0 @@
-(library
- (name picos_sync)
- (public_name picos.sync)
- (libraries picos backoff multicore-magic))
-
-(mdx
- (enabled_if
-  (>= %{ocaml_version} 5.0.0))
- (libraries picos.exn_bt picos.fifos picos.structured picos.sync)
- (files picos_sync.mli))
diff --git a/lib/picos_thread_atomic/dune b/lib/picos_thread_atomic/dune
deleted file mode 100644
index a61af8f..0000000
--- a/lib/picos_thread_atomic/dune
+++ /dev/null
@@ -1,3 +0,0 @@
-(library
- (name picos_thread_atomic)
- (package picos))
diff --git a/lib/picos_threaded/select.some.ml b/lib/picos_threaded/select.some.ml
deleted file mode 100644
index aa39c6b..0000000
--- a/lib/picos_threaded/select.some.ml
+++ /dev/null
@@ -1,2 +0,0 @@
-let cancel_after = Picos_select.cancel_after
-let check_configured = Picos_select.check_configured
diff --git a/picos.opam b/picos.opam
index 694ae3d..afc2f70 100644
--- a/picos.opam
+++ b/picos.opam
@@ -12,34 +12,7 @@ depends: [
   "dune" {>= "3.14"}
   "backoff" {>= "0.1.0"}
   "thread-local-storage" {>= "0.2"}
-  "mtime" {>= "2.0.0"}
-  "psq" {>= "0.2.1"}
-  "multicore-magic" {>= "2.3.0"}
-  "lwt" {>= "5.7.0"}
-  "cohttp" {>= "6.0.0~beta2"}
-  "uri" {>= "4.4.0"}
-  "fmt" {>= "0.9.0"}
-  "multicore-bench" {>= "0.1.4" & with-test}
-  "alcotest" {>= "1.7.0" & with-test}
-  "qcheck-core" {>= "0.21.2" & with-test}
-  "qcheck-stm" {>= "0.3" & with-test}
-  "qcheck-multicoretests-util" {>= "0.3" & with-test}
-  "mdx" {>= "2.4.0" & with-test}
-  "ocaml-version" {>= "3.6.4" & with-test}
-  "domain_shims" {>= "0.1.0" & with-test}
-  "js_of_ocaml" {>= "5.4.0" & with-test}
-  "conf-npm"
-    {arch != "x86_32" & arch != "riscv64" & os != "win32" & with-test}
-  "dscheck" {>= "0.4.0" & with-test}
-  "multicore-magic-dscheck" {>= "2.3.0" & with-test}
-  "sherlodoc" {>= "0.2" & with-doc}
-  "odoc" {>= "2.4.1" & with-doc}
-  "ocaml" {>= "4.14.0"}
-]
-depopts: [
-  "cohttp-lwt" {>= "5.3.0" & with-test}
-  "cohttp-lwt-unix" {>= "5.3.0" & with-test}
-  "conduit-lwt-unix" {>= "6.2.2" & with-test}
+  "odoc" {with-doc}
 ]
 build: [
   ["dune" "subst"] {dev}
diff --git a/picos_aux.opam b/picos_aux.opam
new file mode 100644
index 0000000..b1bef81
--- /dev/null
+++ b/picos_aux.opam
@@ -0,0 +1,30 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Auxiliary libraries for Picos"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "ocaml" {>= "4.14.0"}
+  "backoff" {>= "0.1.0"}
+  "multicore-magic" {>= "2.3.0"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_lwt.opam b/picos_lwt.opam
new file mode 100644
index 0000000..20565f8
--- /dev/null
+++ b/picos_lwt.opam
@@ -0,0 +1,30 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Lwt interface for Picos"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos_aux" {= version}
+  "picos_std" {= version}
+  "lwt" {>= "5.7.0"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_meta.opam b/picos_meta.opam
new file mode 100644
index 0000000..595ecfb
--- /dev/null
+++ b/picos_meta.opam
@@ -0,0 +1,56 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Integration tests for Picos packages"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos" {= version}
+  "picos_aux" {= version}
+  "picos_std" {= version}
+  "picos_stdio" {= version}
+  "picos_stdio_cohttp" {= version}
+  "picos_mux" {= version}
+  "picos_lwt" {= version}
+  "dscheck" {>= "0.4.0"}
+  "lwt" {>= "5.7.0"}
+  "qcheck-core" {>= "0.21.2"}
+  "qcheck-stm" {>= "0.3"}
+  "alcotest" {>= "1.7.0" & with-test}
+  "backoff" {>= "0.1.0" & with-test}
+  "cohttp" {>= "6.0.0~beta2" & with-test}
+  "cohttp-lwt-unix" {>= "6.0.0~beta2" & os != "win32" & with-test}
+  "conduit-lwt-unix" {>= "6.2.2" & os != "win32" & with-test}
+  "conf-npm"
+    {arch != "x86_32" & arch != "riscv64" & os != "win32" & with-test}
+  "domain_shims" {>= "0.1.0" & with-test}
+  "js_of_ocaml" {>= "5.4.0" & with-test}
+  "mdx" {>= "2.4.0" & with-test}
+  "multicore-bench" {>= "0.1.4" & with-test}
+  "multicore-magic" {>= "2.3.0" & with-test}
+  "multicore-magic-dscheck" {>= "2.3.0" & with-test}
+  "ocaml-version" {>= "3.6.4" & with-test}
+  "cohttp-lwt" {>= "6.0.0~beta2" & with-test}
+  "qcheck-multicoretests-util" {>= "0.3" & with-test}
+  "uri" {>= "4.4.0" & with-test}
+  "odoc" {>= "2.4.1" & with-doc}
+  "sherlodoc" {>= "0.2" & with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_mux.opam b/picos_mux.opam
new file mode 100644
index 0000000..f9ef35d
--- /dev/null
+++ b/picos_mux.opam
@@ -0,0 +1,34 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Sample schedulers for Picos"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos_aux" {= version}
+  "picos_std" {= version}
+  "backoff" {>= "0.1.0"}
+  "multicore-magic" {>= "2.3.0"}
+  "odoc" {with-doc}
+]
+depopts: [
+  "picos_stdio" {= version}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_std.opam b/picos_std.opam
new file mode 100644
index 0000000..96a32ca
--- /dev/null
+++ b/picos_std.opam
@@ -0,0 +1,30 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Sample libraries for Picos"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos" {= version}
+  "backoff" {>= "0.1.0"}
+  "multicore-magic" {>= "2.3.0"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_stdio.opam b/picos_stdio.opam
new file mode 100644
index 0000000..91c67da
--- /dev/null
+++ b/picos_stdio.opam
@@ -0,0 +1,33 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Asynchronous IO system for Picos"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos_aux" {= version}
+  "picos_std" {= version}
+  "backoff" {>= "0.1.0"}
+  "mtime" {>= "2.0.0"}
+  "multicore-magic" {>= "2.3.0"}
+  "psq" {>= "0.2.1"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"
diff --git a/picos_stdio_cohttp.opam b/picos_stdio_cohttp.opam
new file mode 100644
index 0000000..58418df
--- /dev/null
+++ b/picos_stdio_cohttp.opam
@@ -0,0 +1,31 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Cohttp running on Picos stdio"
+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
+license: "ISC"
+homepage: "https://github.com/ocaml-multicore/picos"
+bug-reports: "https://github.com/ocaml-multicore/picos/issues"
+depends: [
+  "dune" {>= "3.14"}
+  "picos_stdio" {= version}
+  "cohttp" {>= "6.0.0~beta2"}
+  "fmt" {>= "0.9.0"}
+  "uri" {>= "4.4.0"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/ocaml-multicore/picos.git"


Test patch:
diff --git a/test/dune b/test/dune
index fb2cb72..bdc1b8e 100644
--- a/test/dune
+++ b/test/dune
@@ -1,50 +1,55 @@
 (library
+ (package picos_meta)
  (name test_scheduler)
  (modules test_scheduler)
  (libraries
   (re_export picos)
-  picos.select
+  picos_stdio.select
   (select
    test_scheduler.ml
    from
-   (picos.fifos
-    picos.multififos
-    picos.randos
-    picos.lwt_unix
-    picos.thread
+   (picos.thread
+    picos_lwt.unix
+    picos_mux.random
+    picos_mux.fifo
+    picos_mux.multififo
     ->
     test_scheduler.ocaml5.ml)
-   (picos.threaded lwt.unix -> test_scheduler.ocaml4.ml))))
+   (picos_mux.thread lwt.unix -> test_scheduler.ocaml4.ml))))
 
 (library
+ (package picos_meta)
  (name test_util)
  (modules test_util))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_picos)
  (modules test_picos)
  (libraries
-  test_scheduler
-  picos.finally
-  backoff
   alcotest
-  unix
+  backoff
+  domain_shims
+  picos_std.finally
+  test_scheduler
   threads.posix
-  domain_shims))
+  unix))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_sync)
  (modules test_sync)
  (libraries
-  picos.structured
-  picos.sync
-  test_scheduler
   alcotest
   domain_shims
+  picos_std.event
+  picos_std.structured
+  picos_std.sync
+  test_scheduler
   unix)
  (action
   (progn
@@ -60,113 +65,129 @@
 ;;
 
 (test
+ (package picos_meta)
  (name test_lwt_unix)
  (modules test_lwt_unix)
  (build_if
   (>= %{ocaml_version} 5.0.0))
- (libraries picos.lwt_unix picos.structured alcotest))
+ (libraries picos_lwt.unix picos_std.structured alcotest))
 
 ;;
 
 (rule
+ (package picos_meta)
  (action
   (progn
    (copy ../lib/picos/bootstrap/picos_bootstrap.ml picos_bootstrap.ml))))
 
 (test
+ (package picos_meta)
  (name test_picos_dscheck)
  (modules test_picos_dscheck picos_bootstrap)
  (build_if
   (>= %{ocaml_version} 5))
- (libraries picos.exn_bt backoff traced_atomic dscheck alcotest))
+ (libraries picos backoff traced_atomic dscheck alcotest))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_server_and_client)
  (modules test_server_and_client)
  (libraries
-  test_scheduler
-  picos.finally
-  picos.stdio
-  picos.structured
-  picos.sync))
+  picos_std.finally
+  picos_std.structured
+  picos_std.sync
+  picos_stdio
+  test_scheduler))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_stdio)
  (modules test_stdio)
  (libraries
-  test_scheduler
-  picos.finally
-  picos.mpscq
-  picos.stdio
-  picos.structured
-  alcotest))
+  alcotest
+  picos_aux.mpscq
+  picos_std.finally
+  picos_std.structured
+  picos_stdio
+  test_scheduler))
 
 (test
+ (package picos_meta)
  (name test_stdio_cohttp)
  (modules test_stdio_cohttp)
- (libraries picos.finally picos.stdio.cohttp picos.structured test_scheduler))
+ (libraries
+  picos_std.finally
+  picos_std.structured
+  picos_stdio_cohttp
+  test_scheduler))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_stdio_with_lwt)
  (modules test_stdio_with_lwt)
  (libraries
-  test_scheduler
-  picos.stdio
-  picos.select
   alcotest
   lwt
   lwt.unix
+  picos_stdio
+  picos_stdio.select
+  test_scheduler
   threads.posix))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_select)
  (modules test_select)
  (libraries
-  picos.finally
-  picos.structured
-  picos.threaded
-  picos.select
   alcotest
-  domain_shims))
+  domain_shims
+  picos_mux.thread
+  picos_std.finally
+  picos_std.structured
+  picos_stdio.select))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_mpscq)
  (modules test_mpscq)
  (libraries
-  picos.mpscq
+  picos_aux.mpscq
   qcheck-core
   qcheck-multicoretests-util
   qcheck-stm.stm
   stm_run))
 
 (test
+ (package picos_meta)
  (name test_mpmcq)
  (modules test_mpmcq)
  (libraries
-  picos.mpmcq
+  picos_aux.mpmcq
   qcheck-core
   qcheck-multicoretests-util
   qcheck-stm.stm
   stm_run))
 
 (rule
+ (package picos_meta)
  (action
   (progn
-   (copy ../lib/picos_mpmcq/picos_mpmcq.ml picos_mpmcq.ml))))
+   (copy ../lib/picos_aux.mpmcq/picos_aux_mpmcq.ml picos_aux_mpmcq.ml))))
 
 (test
+ (package picos_meta)
  (name test_mpmcq_dscheck)
- (modules test_mpmcq_dscheck picos_mpmcq)
+ (modules test_mpmcq_dscheck picos_aux_mpmcq)
  (build_if
   (>= %{ocaml_version} 5))
  (libraries backoff multicore-magic-dscheck dscheck alcotest)
@@ -174,10 +195,11 @@
   (:standard -open Multicore_magic_dscheck)))
 
 (test
+ (package picos_meta)
  (name test_htbl)
  (modules test_htbl)
  (libraries
-  picos.htbl
+  picos_aux.htbl
   qcheck-core
   qcheck-multicoretests-util
   qcheck-stm.stm
@@ -186,6 +208,7 @@
 ;;
 
 (test
+ (package picos_meta)
  (name test_js_of_ocaml)
  (modules test_js_of_ocaml)
  (modes js)
@@ -200,49 +223,56 @@
 ;;
 
 (test
+ (package picos_meta)
  (name test_schedulers)
  (modules test_schedulers)
- (libraries picos picos.structured test_scheduler test_util alcotest))
+ (libraries
+  alcotest
+  picos
+  picos_std.structured
+  picos_std.sync
+  test_scheduler
+  test_util))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_structured)
  (modules test_structured)
  (libraries
+  alcotest
   picos
-  picos.mpscq
-  picos.structured
-  picos.sync
+  picos_aux.mpscq
+  picos_std.structured
+  picos_std.sync
   test_scheduler
-  test_util
-  alcotest))
+  test_util))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_finally)
  (modules test_finally)
- (libraries picos picos.finally test_scheduler test_util alcotest))
+ (libraries alcotest picos picos_std.finally test_scheduler test_util))
 
 ;;
 
 (test
+ (package picos_meta)
  (name test_picos_lwt_unix_with_cohttp)
  (modules test_picos_lwt_unix_with_cohttp)
  (build_if
   (and
    (>= %{ocaml_version} 5)
-   %{lib-available:cohttp-lwt-unix}
-   %{lib-available:cohttp-lwt}
-   %{lib-available:cohttp}
-   %{lib-available:conduit-lwt-unix}
-   %{lib-available:uri}))
+   %{lib-available:conduit-lwt-unix}))
  (libraries
-  picos.lwt_unix
-  picos.structured
   cohttp
   cohttp-lwt
   cohttp-lwt-unix
   conduit-lwt-unix
+  picos_lwt.unix
+  picos_std.structured
+  unix
   uri))
diff --git a/test/lib/traced_atomic/dune b/test/lib/traced_atomic/dune
index 948e19e..a9932aa 100644
--- a/test/lib/traced_atomic/dune
+++ b/test/lib/traced_atomic/dune
@@ -1,4 +1,5 @@
 (library
+ (package picos_meta)
  (name traced_atomic)
  (wrapped false)
  (enabled_if
diff --git a/test/stm_run/dune b/test/stm_run/dune
index c95ac91..5937a80 100644
--- a/test/stm_run/dune
+++ b/test/stm_run/dune
@@ -1,15 +1,18 @@
 (rule
+ (package picos_meta)
  (enabled_if %{lib-available:qcheck-stm.domain})
  (action
   (copy stm_run.ocaml5.ml stm_run.ml)))
 
 (rule
+ (package picos_meta)
  (enabled_if
   (not %{lib-available:qcheck-stm.domain}))
  (action
   (copy stm_run.ocaml4.ml stm_run.ml)))
 
 (library
+ (package picos_meta)
  (name stm_run)
  (libraries
   qcheck-core
diff --git a/test/test_finally.ml b/test/test_finally.ml
index 8669e9e..342add0 100644
--- a/test/test_finally.ml
+++ b/test/test_finally.ml
@@ -1,4 +1,4 @@
-open Picos_finally
+open Picos_std_finally
 
 let test_move_is_lazy () =
   Test_scheduler.run @@ fun () ->
diff --git a/test/test_htbl.ml b/test/test_htbl.ml
index cc4e781..55829a6 100644
--- a/test/test_htbl.ml
+++ b/test/test_htbl.ml
@@ -1,21 +1,21 @@
 open QCheck
 open STM
-module Htbl = Picos_htbl
+module Htbl = Picos_aux_htbl
 
 let () =
   (* Basics *)
   Random.self_init ();
-  let t = Picos_htbl.create () in
-  assert (Picos_htbl.try_add t "Basics" 101);
-  assert (Picos_htbl.try_add t "Answer" 42);
-  assert (101 = Picos_htbl.remove_exn t "Basics");
-  assert (not (Picos_htbl.try_remove t "Basics"));
-  assert (Picos_htbl.remove_all t |> List.of_seq = [ ("Answer", 42) ]);
-  assert (Picos_htbl.to_seq t |> List.of_seq = []);
+  let t = Htbl.create () in
+  assert (Htbl.try_add t "Basics" 101);
+  assert (Htbl.try_add t "Answer" 42);
+  assert (101 = Htbl.remove_exn t "Basics");
+  assert (not (Htbl.try_remove t "Basics"));
+  assert (Htbl.remove_all t |> List.of_seq = [ ("Answer", 42) ]);
+  assert (Htbl.to_seq t |> List.of_seq = []);
   [ "One"; "Two"; "Three" ]
-  |> List.iteri (fun v k -> assert (Picos_htbl.try_add t k v));
+  |> List.iteri (fun v k -> assert (Htbl.try_add t k v));
   assert (
-    Picos_htbl.to_seq t |> List.of_seq
+    Htbl.to_seq t |> List.of_seq
     |> List.sort (fun l r -> String.compare (fst l) (fst r))
     = [ ("One", 0); ("Three", 2); ("Two", 1) ])
 
@@ -93,4 +93,4 @@ module Spec = struct
     | _, _ -> false
 end
 
-let () = Stm_run.run ~name:"Picos_htbl" (module Spec) |> exit
+let () = Stm_run.run ~name:"Htbl" (module Spec) |> exit
diff --git a/test/test_lwt_unix.ml b/test/test_lwt_unix.ml
index 7782422..4f4279d 100644
--- a/test/test_lwt_unix.ml
+++ b/test/test_lwt_unix.ml
@@ -1,4 +1,4 @@
-open Picos_structured
+open Picos_std_structured
 
 let basics () =
   Lwt_main.run @@ Picos_lwt_unix.run
diff --git a/test/test_mpmcq.ml b/test/test_mpmcq.ml
index bd6bf7c..179d8a6 100644
--- a/test/test_mpmcq.ml
+++ b/test/test_mpmcq.ml
@@ -1,6 +1,6 @@
 open QCheck
 open STM
-module Queue = Picos_mpmcq
+module Queue = Picos_aux_mpmcq
 
 let () =
   let q = Queue.create () in
@@ -80,4 +80,4 @@ module Spec = struct
     | _, _ -> false
 end
 
-let () = Stm_run.run ~name:"Picos_mpmcq" (module Spec) |> exit
+let () = Stm_run.run ~name:"Mpmcq" (module Spec) |> exit
diff --git a/test/test_mpmcq_dscheck.ml b/test/test_mpmcq_dscheck.ml
index 204c050..d501163 100644
--- a/test/test_mpmcq_dscheck.ml
+++ b/test/test_mpmcq_dscheck.ml
@@ -1,12 +1,8 @@
 module Atomic = Dscheck.TracedAtomic
-module Q = Picos_mpmcq
+module Q = Picos_aux_mpmcq
 
 let popped_some q =
-  let b =
-    match Picos_mpmcq.pop_exn q with
-    | _ -> true
-    | exception Picos_mpmcq.Empty -> false
-  in
+  let b = match Q.pop_exn q with _ -> true | exception Q.Empty -> false in
   Atomic.check (fun () -> b)
 
 let test_multi_push_pop () =
diff --git a/test/test_mpscq.ml b/test/test_mpscq.ml
index c7e8a64..f71ad9e 100644
--- a/test/test_mpscq.ml
+++ b/test/test_mpscq.ml
@@ -1,4 +1,4 @@
-module Queue = Picos_mpscq
+module Queue = Picos_aux_mpscq
 
 module Spec = struct
   type cmd = Push of int | Push_head of int | Pop | Pop_all
@@ -87,4 +87,4 @@ let () =
     in
     [ agree_test_par_asym ~count ~name:(name ^ " parallel") ]
   in
-  Stm_run.run ~name:"Picos_mpscq" ~make_domain (module Spec) |> exit
+  Stm_run.run ~name:"Mpscq" ~make_domain (module Spec) |> exit
diff --git a/test/test_picos.ml b/test/test_picos.ml
index 2084bd3..cbf4f53 100644
--- a/test/test_picos.ml
+++ b/test/test_picos.ml
@@ -1,5 +1,5 @@
 open Picos
-open Picos_finally
+open Picos_std_finally
 
 let run_in_fiber main =
   let computation = Computation.create ~mode:`LIFO () in
@@ -52,12 +52,14 @@ let test_trigger_basics () =
     finally Domain.join @@ fun () ->
     Domain.spawn @@ fun () -> Trigger.signal trigger
   in
-  Trigger.await trigger |> Option.iter Exn_bt.raise
+  match Trigger.await trigger with
+  | None -> ()
+  | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
 
 let test_computation_basics () =
   Test_scheduler.run @@ fun () ->
   let computation = Computation.create () in
-  Computation.cancel computation (Exn_bt.get Exit);
+  Computation.cancel computation Exit (Printexc.get_callstack 2);
   Computation.wait computation;
   let computation = Computation.create () in
   let@ _ =
@@ -74,7 +76,7 @@ let test_computation_basics () =
     finally Domain.join @@ fun () ->
     Domain.spawn @@ fun () ->
     Unix.sleepf 0.01;
-    Computation.cancel computation (Exn_bt.get_callstack 2 Exit)
+    Computation.cancel computation Exit (Printexc.get_callstack 2)
   in
   Alcotest.check_raises "should be canceled" Exit @@ fun () ->
   let _ : int = Computation.await computation in
@@ -82,7 +84,7 @@ let test_computation_basics () =
 
 let test_computation_tx () =
   let module Tx = Computation.Tx in
-  let exit_bt = Exn_bt.get_callstack 0 Exit in
+  let empty_bt = Printexc.get_callstack 0 in
   let n_case_1 = ref 0 and n_case_2 = ref 0 in
   let n = 1_000 in
   let deadline = Unix.gettimeofday () +. 60.0 in
@@ -103,7 +105,8 @@ let test_computation_tx () =
           Computation.is_running a
           && not
                (let tx = Tx.create () in
-                Tx.try_return tx a 101 && Tx.try_cancel tx b exit_bt
+                Tx.try_return tx a 101
+                && Tx.try_cancel tx b Exit empty_bt
                 && Tx.try_commit tx)
         do
           backoff := Backoff.once !backoff
@@ -122,8 +125,9 @@ let test_computation_tx () =
           && not
                (let tx = Tx.create () in
                 not
-                  (Tx.try_return tx b 42 && Tx.try_cancel tx a exit_bt
-                 && Tx.try_commit tx))
+                  (Tx.try_return tx b 42
+                  && Tx.try_cancel tx a Exit empty_bt
+                  && Tx.try_commit tx))
         do
           backoff := Backoff.once !backoff
         done
@@ -137,10 +141,10 @@ let test_computation_tx () =
     done;
     if
       Computation.peek a = Some (Ok 101)
-      && Computation.peek b = Some (Error exit_bt)
+      && Computation.peek b = Some (Error (Exit, empty_bt))
     then incr n_case_1
     else if
-      Computation.peek a = Some (Error exit_bt)
+      Computation.peek a = Some (Error (Exit, empty_bt))
       && Computation.peek b = Some (Ok 42)
     then incr n_case_2
     else assert false
@@ -168,7 +172,7 @@ let test_cancel () =
     Fiber.spawn (Fiber.create ~forbid:false computation) main;
     result
   in
-  Computation.cancel computation (Exn_bt.get_callstack 0 Exit)
+  Computation.cancel computation Exit (Printexc.get_callstack 0)
 
 let test_cancel_after () =
   Alcotest.check_raises "should be canceled" Not_found @@ fun () ->
@@ -183,8 +187,8 @@ let test_cancel_after () =
       ()
   in
   Fiber.spawn (Fiber.create ~forbid:false computation) main;
-  Computation.cancel_after computation ~seconds:0.01
-    (Exn_bt.get_callstack 0 Not_found);
+  Computation.cancel_after computation ~seconds:0.01 Not_found
+    (Printexc.get_callstack 0);
   Computation.await computation
 
 let test_computation_completion_signals_triggers_in_order () =
diff --git a/test/test_picos_dscheck.ml b/test/test_picos_dscheck.ml
index cf170b1..b29fe4f 100644
--- a/test/test_picos_dscheck.ml
+++ b/test/test_picos_dscheck.ml
@@ -56,7 +56,7 @@ let test_computation_contract () =
        in
        let () =
          Atomic.spawn @@ fun () ->
-         if Computation.try_cancel computation (Exn_bt.get_callstack 1 Exit)
+         if Computation.try_cancel computation Exit (Printexc.get_callstack 1)
          then incr cancels
        in
        let triggers = Array.init 2 @@ fun _ -> Trigger.create () in
@@ -75,8 +75,7 @@ let test_computation_contract () =
        begin
          match Computation.peek computation with
          | Some (Ok 101) when !returns = 1 && !cancels = 0 -> true
-         | Some (Error { exn = Exit; _ }) when !returns = 0 && !cancels = 1 ->
-             true
+         | Some (Error (Exit, _)) when !returns = 0 && !cancels = 1 -> true
          | _ -> false
        end
        && !attached + !unattached = Array.length triggers
diff --git a/test/test_picos_lwt_unix_with_cohttp.ml b/test/test_picos_lwt_unix_with_cohttp.ml
index 503a458..b7c0eb4 100644
--- a/test/test_picos_lwt_unix_with_cohttp.ml
+++ b/test/test_picos_lwt_unix_with_cohttp.ml
@@ -1,8 +1,10 @@
-open Picos_structured
+open Picos_std_structured
 open Cohttp
 open Cohttp_lwt
 open Cohttp_lwt_unix
 
+let port = 8000
+
 (** [Picos_lwt] gives us a direct-style [await] operation. *)
 let await = Picos_lwt.await
 
@@ -25,17 +27,24 @@ let main () =
     in
     let ctx = Conduit_lwt_unix.init ~src:"127.0.0.1" () |> await in
     let ctx = Client.custom_ctx ~ctx () in
-    Server.create ~ctx ~mode:(`TCP (`Port 8000)) (Server.make ~callback ())
+    Server.create ~ctx ~mode:(`TCP (`Port port)) (Server.make ~callback ())
     |> await
   in
 
   (* Then we GET a response from the server. *)
   let _resp, body =
-    await (Client.get (Uri.of_string "http://127.0.0.1:8000/hello-lwt"))
+    await
+      (Client.get
+         (Uri.of_string (Printf.sprintf "http://127.0.0.1:%d/hello-lwt" port)))
   in
   Printf.printf "%s\n%!" (await (Body.to_string body));
 
   (* Finally we terminate the server. *)
   Promise.terminate server
 
-let () = Lwt_main.run @@ Picos_lwt_unix.run main
+let () =
+  Lwt_main.run @@ Picos_lwt_unix.run
+  @@ fun () ->
+  try main ()
+  with Unix.Unix_error _ as exn ->
+    Printf.printf "%s\n%!" (Printexc.to_string exn)
diff --git a/test/test_scheduler.ocaml4.ml b/test/test_scheduler.ocaml4.ml
index df04791..6c4ac63 100644
--- a/test/test_scheduler.ocaml4.ml
+++ b/test/test_scheduler.ocaml4.ml
@@ -4,7 +4,7 @@ let () = Random.self_init ()
 open Picos
 
 let () =
-  Picos_select.check_configured ();
+  Picos_stdio_select.check_configured ();
 
   let[@alert "-handler"] rec propagate () =
     let computation =
@@ -13,12 +13,12 @@ let () =
       Lwt_unix.handle_signal Sys.sigchld;
       propagate ()
     in
-    Picos_select.return_on_sigchld computation ()
+    Picos_stdio_select.return_on_sigchld computation ()
   in
   propagate ()
 
 let run_fiber ?max_domains:_ ?allow_lwt:_ ?fatal_exn_handler fiber main =
-  Picos_threaded.run_fiber ?fatal_exn_handler fiber main
+  Picos_mux_thread.run_fiber ?fatal_exn_handler fiber main
 
 let run ?max_domains ?allow_lwt ?fatal_exn_handler ?(forbid = false) main =
   let computation = Computation.create ~mode:`LIFO () in
diff --git a/test/test_scheduler.ocaml5.ml b/test/test_scheduler.ocaml5.ml
index f6ae380..7fa0e48 100644
--- a/test/test_scheduler.ocaml5.ml
+++ b/test/test_scheduler.ocaml5.ml
@@ -4,7 +4,7 @@ let () = Random.self_init ()
 open Picos
 
 let () =
-  Picos_select.check_configured ();
+  Picos_stdio_select.check_configured ();
 
   let[@alert "-handler"] rec propagate () =
     let computation =
@@ -13,7 +13,7 @@ let () =
       Lwt_unix.handle_signal Sys.sigchld;
       propagate ()
     in
-    Picos_select.return_on_sigchld computation ()
+    Picos_stdio_select.return_on_sigchld computation ()
   in
   propagate ()
 
@@ -51,15 +51,17 @@ let rec run_fiber ?(max_domains = 1) ?(allow_lwt = true) ?fatal_exn_handler
     | `Randos ->
         Some
           (fun () ->
-            Picos_randos.run_fiber_on ?fatal_exn_handler ~n_domains fiber main)
+            Picos_mux_random.run_fiber_on ?fatal_exn_handler ~n_domains fiber
+              main)
     | `Fifos ->
         Some
-          (fun () -> Picos_fifos.run_fiber ~quota ?fatal_exn_handler fiber main)
+          (fun () ->
+            Picos_mux_fifo.run_fiber ~quota ?fatal_exn_handler fiber main)
     | `Multififos ->
         Some
           (fun () ->
-            Picos_multififos.run_fiber_on ~quota ?fatal_exn_handler ~n_domains
-              fiber main)
+            Picos_mux_multififo.run_fiber_on ~quota ?fatal_exn_handler
+              ~n_domains fiber main)
   with
   | None -> run_fiber ~max_domains ~allow_lwt ?fatal_exn_handler fiber main
   | Some run -> begin
diff --git a/test/test_schedulers.ml b/test/test_schedulers.ml
index f883c1c..5ff7542 100644
--- a/test/test_schedulers.ml
+++ b/test/test_schedulers.ml
@@ -1,8 +1,8 @@
 open Picos
-open Picos_structured
-open Picos_sync
+open Picos_std_structured
+open Picos_std_sync
 
-let exit_exn_bt = Exn_bt.get_callstack 0 Exit
+let empty_bt = Printexc.get_callstack 0
 
 let test_returns () =
   let actual = Test_scheduler.run @@ fun () -> 42 in
@@ -46,13 +46,13 @@ let test_current () =
 let test_cancel_after_basic () =
   Test_scheduler.run @@ fun () ->
   let computation = Computation.create () in
-  Computation.cancel_after computation ~seconds:0.1 exit_exn_bt;
+  Computation.cancel_after computation ~seconds:0.1 Exit empty_bt;
   Computation.wait computation
 
 let test_cancel_after_long_timeout () =
   Test_scheduler.run @@ fun () ->
   let computation = Computation.create () in
-  match Computation.cancel_after computation ~seconds:10e10 exit_exn_bt with
+  match Computation.cancel_after computation ~seconds:10e10 Exit empty_bt with
   | () -> Computation.finish computation
   | exception Invalid_argument _ -> ()
 
@@ -75,8 +75,8 @@ let test_op_raises_when_canceled () =
         Flock.fork_as_promise @@ fun () ->
         wait ();
         match
-          Computation.cancel_after (Computation.create ()) ~seconds:1.0
-            (Exn_bt.get_callstack 0 Exit)
+          Computation.cancel_after (Computation.create ()) ~seconds:1.0 Exit
+            empty_bt
         with
         | () -> assert false
         | exception Control.Terminate -> ()
@@ -112,7 +112,7 @@ let test_fatal () =
     let computation = Computation.create () in
     let fiber = Fiber.create ~forbid:false computation in
     let fatal_exn_handler exn =
-      Computation.cancel computation (Exn_bt.get exn);
+      Computation.cancel computation exn (Printexc.get_raw_backtrace ());
       raise exn
     in
     Test_scheduler.run_fiber ~fatal_exn_handler ~max_domains:3 fiber @@ fun _ ->
diff --git a/test/test_select.ml b/test/test_select.ml
index c888106..9a231c9 100644
--- a/test/test_select.ml
+++ b/test/test_select.ml
@@ -1,7 +1,7 @@
-open Picos_structured
-open Picos_finally
+open Picos_std_structured
+open Picos_std_finally
 
-let () = Picos_select.configure ()
+let () = Picos_stdio_select.configure ()
 
 let test_intr () =
   let@ inn, _out =
@@ -11,15 +11,16 @@ let test_intr () =
     @@ Unix.pipe ~cloexec:true
   in
   let main () =
-    Picos_threaded.run @@ fun () ->
+    Picos_mux_thread.run @@ fun () ->
     Flock.join_after @@ fun () ->
     for _ = 1 to 10 do
       Flock.fork @@ fun () ->
       for _ = 1 to 1_000 do
-        let req = Picos_select.Intr.req ~seconds:0.000_001 in
+        let req = Picos_stdio_select.Intr.req ~seconds:0.000_001 in
         match Unix.read inn (Bytes.create 1) 0 1 with
         | _ -> assert false
-        | exception Unix.Unix_error (EINTR, _, _) -> Picos_select.Intr.clr req
+        | exception Unix.Unix_error (EINTR, _, _) ->
+            Picos_stdio_select.Intr.clr req
       done
     done
   in
diff --git a/test/test_server_and_client.ml b/test/test_server_and_client.ml
index 2fa3175..ccd880f 100644
--- a/test/test_server_and_client.ml
+++ b/test/test_server_and_client.ml
@@ -1,7 +1,7 @@
-open Picos_finally
-open Picos_structured
+open Picos_std_finally
+open Picos_std_structured
 open Picos_stdio
-open Picos_sync
+open Picos_std_sync
 
 let is_ocaml4 = String.starts_with ~prefix:"4." Sys.ocaml_version
 let use_nonblock = Sys.win32 || Random.bool ()
diff --git a/test/test_stdio.ml b/test/test_stdio.ml
index 2af6bb1..3eb8501 100644
--- a/test/test_stdio.ml
+++ b/test/test_stdio.ml
@@ -1,6 +1,7 @@
-open Picos_finally
-open Picos_structured
+open Picos_std_finally
+open Picos_std_structured
 open Picos_stdio
+module Mpscq = Picos_aux_mpscq
 
 let test_system_unix () =
   Test_scheduler.run @@ fun () ->
@@ -76,7 +77,7 @@ let test_select () =
   Unix.set_nonblock syn_inn;
   Unix.set_nonblock syn_out;
 
-  let events = Picos_mpscq.create ~padded:true () in
+  let events = Mpscq.create ~padded:true () in
 
   begin
     Flock.join_after ~on_return:`Terminate @@ fun () ->
@@ -86,17 +87,17 @@ let test_select () =
         match Unix.select [ msg_inn1; msg_inn2 ] [] [] 0.2 with
         | inns, _, _ ->
             if List.exists (( == ) msg_inn1) inns then begin
-              Picos_mpscq.push events (Printf.sprintf "Inn1");
+              Mpscq.push events (Printf.sprintf "Inn1");
               assert (1 = Unix.read msg_inn1 (Bytes.create 1) 0 1);
               assert (1 = Unix.write_substring syn_out "!" 0 1)
             end;
             if List.exists (( == ) msg_inn2) inns then begin
-              Picos_mpscq.push events (Printf.sprintf "Inn2");
+              Mpscq.push events (Printf.sprintf "Inn2");
               assert (1 = Unix.read msg_inn2 (Bytes.create 1) 0 1);
               assert (1 = Unix.write_substring syn_out "!" 0 1)
             end;
             if [] == inns then begin
-              Picos_mpscq.push events (Printf.sprintf "Timeout");
+              Mpscq.push events (Printf.sprintf "Timeout");
               assert (1 = Unix.write_substring syn_out "!" 0 1)
             end
       done
@@ -112,7 +113,7 @@ let test_select () =
     assert (1 = Unix.read syn_inn (Bytes.create 1) 0 1)
   end;
 
-  let events = Picos_mpscq.pop_all events in
+  let events = Mpscq.pop_all events in
 
   assert (Seq.exists (( = ) "Timeout") events);
   assert (
diff --git a/test/test_stdio_cohttp.ml b/test/test_stdio_cohttp.ml
index e01489c..6b98fab 100644
--- a/test/test_stdio_cohttp.ml
+++ b/test/test_stdio_cohttp.ml
@@ -1,8 +1,8 @@
 open Cohttp
-open Picos_finally
+open Picos_std_finally
+open Picos_std_structured
 open Picos_stdio
 open Picos_stdio_cohttp
-open Picos_structured
 
 let is_opam_ci =
   match Sys.getenv "OPAM_REPO_CI" with
diff --git a/test/test_structured.ml b/test/test_structured.ml
index 9fa369a..0befa6b 100644
--- a/test/test_structured.ml
+++ b/test/test_structured.ml
@@ -1,6 +1,7 @@
-module Exn_bt = Picos.Exn_bt
-open Picos_structured
-open Picos_sync
+open Picos_std_event
+open Picos_std_structured
+open Picos_std_sync
+module Mpscq = Picos_aux_mpscq
 
 (** Helper to check that computation is restored *)
 let check join_after ?callstack ?on_return scope =
@@ -181,9 +182,9 @@ let test_any_and_all_errors () =
   [ Run.all; Run.any ]
   |> List.iter @@ fun run_op ->
      Test_scheduler.run ~max_domains:6 @@ fun () ->
-     let raised = Picos_mpscq.create ~padded:true () in
+     let raised = Mpscq.create ~padded:true () in
      let raiser exn () =
-       Picos_mpscq.push raised exn;
+       Mpscq.push raised exn;
        raise exn
      in
      match
@@ -204,13 +205,13 @@ let test_any_and_all_errors () =
          let exn_bts =
            match exn with
            | Control.Errors exn_bts -> exn_bts
-           | exn -> [ Exn_bt.get exn ]
+           | exn ->
+               let bt = Printexc.get_raw_backtrace () in
+               [ (exn, bt) ]
          in
-         Picos_mpscq.pop_all raised
+         Mpscq.pop_all raised
          |> Seq.iter @@ fun exn ->
-            assert (
-              exn_bts
-              |> List.exists @@ fun (exn_bt : Exn_bt.t) -> exn == exn_bt.exn)
+            assert (exn_bts |> List.exists @@ fun (exn', _) -> exn == exn')
 
 let test_any_and_all_returns () =
   [ 0; 1; 2 ]
diff --git a/test/test_sync.ml b/test/test_sync.ml
index 677d672..208ecf3 100644
--- a/test/test_sync.ml
+++ b/test/test_sync.ml
@@ -1,8 +1,10 @@
 open Picos
-open Picos_sync
-open Picos_structured
+open Picos_std_event
+open Picos_std_sync
+open Picos_std_structured
 
 let msgs = ref []
+let empty_bt = Printexc.get_callstack 0
 
 module Fiber = struct
   include Fiber
@@ -76,8 +78,6 @@ let test_mutex_and_condition_cancelation () =
 
   let steps = [| step_1; step_2; step_3; step_4; step_5 |] in
 
-  let exit_exn_bt = Exn_bt.get_callstack 0 Exit in
-
   let n = if 4 <= Domain.recommended_domain_count () - 2 then 4 else 2 in
 
   let computations =
@@ -151,7 +151,7 @@ let test_mutex_and_condition_cancelation () =
           let (Packed computation) =
             computations.(Random.State.bits state land (n - 1))
           in
-          Computation.cancel computation exit_exn_bt
+          Computation.cancel computation Exit empty_bt
         done)
     :: List.init n (fun i -> Domain.spawn (main i))
   in
@@ -230,7 +230,7 @@ let test_lazy_cancelation () =
     Option.iter Trigger.signal to_signal;
     match Trigger.await to_await with
     | None -> result
-    | Some exn_bt -> Exn_bt.raise exn_bt
+    | Some (exn, bt) -> Printexc.raise_with_backtrace exn bt
   in
   assert (
     let to_await = Trigger.create () in
@@ -242,7 +242,7 @@ let test_lazy_cancelation () =
       | exception Exit -> Atomic.set tried true
     in
     let computation = Computation.create () in
-    Computation.cancel computation (Exn_bt.get_callstack 0 Exit);
+    Computation.cancel computation Exit empty_bt;
     Fiber.spawn (Fiber.create ~forbid:false computation) main;
     while not (Atomic.get tried) do
       Fiber.yield ()
@@ -255,7 +255,7 @@ let test_lazy_cancelation () =
   let s = susp ~to_signal ~to_await 101 in
   let c = Fiber.start @@ fun () -> Lazy.force s in
   Trigger.await to_signal |> ignore;
-  Computation.cancel c (Exn_bt.get_callstack 0 Exit);
+  Computation.cancel c Exit empty_bt;
   match Lazy.force s with
   | _ -> assert false
   | exception Exit -> Trigger.signal to_await
@@ -271,7 +271,7 @@ let test_event_basics () =
     = 101);
   begin
     let c = Computation.create () in
-    Computation.cancel_after c ~seconds:0.1 (Exn_bt.get_callstack 0 Not_found);
+    Computation.cancel_after c ~seconds:0.1 Not_found empty_bt;
     match Event.sync (Event.from_computation c) with
     | () -> assert false
     | exception Not_found -> ()