diff --git a/LICENSE-HasChor.txt b/LICENSE-HasChor.txt
new file mode 100644
--- /dev/null
+++ b/LICENSE-HasChor.txt
@@ -0,0 +1,32 @@
+MIT License
+
+Copyright (c) 2022, Gan Shen
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Gan Shen nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# CloudChor
+
+CloudChor is a Haskell library for lighweight and efficient choreographic programming with multiply-located values.
+It is an extension of the [HasChor](https://github.com/gshen42/HasChor) library.
+
+For more details, see the PEPM '26 paper: [Towards Lightweight and Efficient Choreographic Cloud Services](https://doi.org/10.1145/3779209.3779537).
+
+## Repository structure
+
+* [`src/`](src/) contains the source code of the library.
+  * [`Choreography.Location.Multi`](src/Choreography/Location/Multi.hs) is the module that implements multiply-located values.
+* [`examples/`](examples/) includes example programs using the library.
+  * [`clean-room`](examples/clean-room/): An implementation of a data clean room protocol as a choreography.
+  * [`unsoundness`](examples/unsoundness): Two counter-examples showing how unrestricted IO can lead to unsoundness.
+  * [`HasChor`](examples/HasChor): The original examples from the HasChor repository.
+* [`bench/`](bench/) contains the setup for running benchmarks to compare `cond` vs. `cond_`.
+
+## License
+
+This repository is provided under the terms of the [Mozilla Public License 2.0](LICENSE.txt).
+It is based on HasChor, which is provided under the [MIT License](LICENSE-HasChor.txt).
diff --git a/bench/Benchmark.hs b/bench/Benchmark.hs
new file mode 100644
--- /dev/null
+++ b/bench/Benchmark.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE ParallelListComp   #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+
+module Benchmark where
+
+import Control.Concurrent
+import Control.Monad(when)
+import Data.Foldable(for_)
+import Data.IORef
+import Data.Proxy(Proxy(..))
+import Data.Time
+import Data.Traversable(for)
+import GHC.TypeLits(KnownSymbol)
+
+import Choreography hiding (cond, cond', cond_, cond_')
+import Choreography qualified as C
+import Choreography.Choreo
+import Choreography.Network
+import Choreography.Network.Local
+import Choreography.Network.LocalMsgCount
+
+-- Benchmarking infrastructure
+
+-- The type of the `cond_'` function, to avoid writing it out multiple times.
+-- NB: This is a more specific version of the original `cond'` function's type.
+type Cond_' =
+  forall m l a b
+  . (Bounded a, Enum a, Show a, Read a, KnownSymbol l)
+  => (Proxy l, Unwrap l -> m a)
+  -> (a -> Choreo m (b @ l))
+  -> Choreo m (b @ l)
+
+httpConfig :: [LocTm] -> HttpConfig
+httpConfig ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000..]]
+
+data SomeBackend = forall b. Backend b => SomeBackend b
+
+runWithBackend :: SomeBackend -> Choreo IO () -> LocTm -> IO ()
+runWithBackend (SomeBackend b) = runChoreography b
+
+mkBackendConfig :: String -> IORef Int -> [LocTm] -> IO SomeBackend
+mkBackendConfig "http"  _ = pure . SomeBackend . httpConfig
+mkBackendConfig "local" _ = fmap SomeBackend . mkLocalConfig
+mkBackendConfig "count" msgCounter = fmap SomeBackend . mkLocalMsgCountConfig msgCounter
+mkBackendConfig _ _ = error "Invalid backend"
+
+timed :: IO () -> IO ()
+timed io = do
+  before <- getCurrentTime
+  io
+  after <- getCurrentTime
+  print $ diffUTCTime after before
+
+-- Run a choreography with both the original `cond'` and the non-broadcasting `cond_'` and report their execution time.
+benchmarkChoreography :: String -> String -> [LocTm] -> (Cond_' -> Choreo IO ()) -> IO ()
+benchmarkChoreography backend cond locs choreo = do
+  let chor = choreo $ if cond == "full" then C.cond' else C.cond_'
+  msgCounter <- newIORef 0
+  someCfg <- mkBackendConfig backend msgCounter locs
+
+  timed do
+    mvars <- for locs \l -> do
+      mvar <- newEmptyMVar
+
+      forkFinally (runWithBackend someCfg chor l) \_ -> putMVar mvar ()
+      pure mvar
+
+    for_ mvars takeMVar
+
+  when (backend == "count") do
+    count <- readIORef msgCounter
+    putStrLn $ "Sends: " <> show count
diff --git a/bench/Choreos/Bookseller.hs b/bench/Choreos/Bookseller.hs
new file mode 100644
--- /dev/null
+++ b/bench/Choreos/Bookseller.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE LambdaCase         #-}
+
+-- This is a version of the `bookseller-1-simple` choreography, adapted for the benchmarking setup.
+module Choreos.Bookseller where
+
+import Data.Proxy
+import Data.Time
+
+import Benchmark
+import Choreography hiding (cond, cond_, cond', cond_')
+
+buyer :: Proxy "buyer"
+buyer = Proxy
+
+seller :: Proxy "seller"
+seller = Proxy
+
+-- `bookseller'` is a simplified version of `bookseller` that utilizes `~~>`
+bookseller' :: Cond_' -> Choreo IO ()
+bookseller' cond_' = do
+  title <- (buyer, \_ -> pure "Types and Programming Languages") ~~> seller
+
+  price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer
+
+  cond_' (buyer, \un -> return $ (un price) < budget) \case
+    True  -> do
+      deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer
+
+      buyer `locally` \un -> do
+        -- putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)
+        return $ Just (un deliveryDate)
+
+    False -> do
+      buyer `locally` \_ -> do
+        -- putStrLn "The book's price is out of the budget"
+        return Nothing
+
+  pure ()
+
+budget :: Int
+budget = 100
+
+priceOf :: String -> Int
+priceOf "Types and Programming Languages" = 80
+priceOf "Homotopy Type Theory"            = 120
+priceOf _                                 = error "Unknown book"
+
+deliveryDateOf :: String -> Day
+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19
+deliveryDateOf "Homotopy Type Theory"            = fromGregorian 2023 01 01
+deliveryDateOf _                                 = error "Unknown book"
diff --git a/bench/Choreos/CleanRoom.hs b/bench/Choreos/CleanRoom.hs
new file mode 100644
--- /dev/null
+++ b/bench/Choreos/CleanRoom.hs
@@ -0,0 +1,160 @@
+{-# OPTIONS_GHC -Wno-redundant-constraints #-}
+
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs              #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE LambdaCase         #-}
+
+-- This is a version of the `clean-room` choreography, adapted for the benchmarking setup.
+module Choreos.CleanRoom where
+
+import Data.Function((&))
+import Data.Proxy(Proxy(..))
+import Data.SOP.BasicFunctors(mapKK, K (..))
+import Data.SOP.Classes(HCollapse(..), hmap, hsequenceK)
+import Data.SOP.Constraint(All)
+import Data.SOP.NP(NP(..))
+import GHC.TypeLits(KnownSymbol, SSymbol, withKnownSymbol, withSomeSSymbol)
+
+import Benchmark
+import Choreography hiding (cond, cond_, cond', cond_')
+import Choreography.Location
+import Choreography.Location.Multi
+
+-- Schema and dataset handling (stubbed out)
+
+data Schema = Schema
+  deriving stock (Show, Read)
+
+data Dataset = Dataset
+  deriving stock (Show, Read)
+
+data Query = Query
+  deriving stock (Show, Read)
+
+loadSchema :: KnownSymbol l => Proxy l -> IO Schema
+loadSchema _ = pure Schema
+
+mergeSchemas :: [Schema] -> Schema
+mergeSchemas _ = Schema
+
+conformsToSchema :: Schema -> Dataset -> IO Bool
+conformsToSchema _ _ = pure True
+
+emptyDataset :: Dataset
+emptyDataset = Dataset
+
+-- Simulate some I/O to load a dataset (e.g. from a file)
+loadDataset :: KnownSymbol l => Proxy l -> IO Dataset
+loadDataset _ = pure Dataset
+
+mergeDatasets :: [Dataset] -> Dataset
+mergeDatasets _ = Dataset
+
+loadQuery :: KnownSymbol l => Proxy l -> Schema -> IO Query
+loadQuery _ _ = pure Query
+
+agreeWithQueries :: KnownSymbol l => Proxy l -> [Query] -> IO Bool
+agreeWithQueries _ _ = pure True
+
+runQuery :: Dataset -> Query -> IO Int
+runQuery _ _ = pure 0
+
+-- Error-handling (if datasets don't conform to schema)
+
+getClientDataset :: (KnownSymbol server, KnownSymbol client) => Cond_' -> Int -> Int -> Schema @ server -> Proxy server -> Proxy client -> Choreo IO (Dataset @ server)
+getClientDataset cond_' failures tries schema server client =
+  if tries <= 0 then
+    pure $ wrap emptyDataset
+  else do
+    dataset <- (client, \_ -> loadDataset client) ~~> server
+    cond_'
+      (server, \unwrap ->
+        if failures > 0 then
+          pure False -- Simulate a fixed number of failures for each client
+        else
+          conformsToSchema (unwrap schema) (unwrap dataset)
+      )
+      \case
+        True -> pure dataset
+        False -> getClientDataset cond_' (failures - 1) (tries - 1) schema server client
+
+getClientDatasets :: (KnownSymbol server, All KnownSymbol clients) => Cond_' -> Int -> Int -> Schema @ server -> Proxy server -> NP Proxy clients -> Choreo IO (NP (K Dataset) clients @ server)
+getClientDatasets _ _ _ _ _ Nil = pure $ wrap Nil
+getClientDatasets cond_' failures tries schema server (client :* clients) = do
+  dataset <- getClientDataset cond_' failures tries schema server client
+  datasets <- getClientDatasets cond_' failures tries schema server clients
+  locally server \unwrap -> pure $ K (unwrap dataset) :* unwrap datasets
+
+-- Main choreography
+
+cleanRoom :: (KnownSymbol server, All KnownSymbol clients) => Cond_' -> Int -> Proxy server -> NP Proxy clients -> Choreo IO ()
+cleanRoom cond_' failures server clients = do
+  -- Every client loads their data schema and sends it to the server
+  schemas <- (clients, \c _ -> loadSchema c) *~~> server
+
+  -- Server merges the schemas
+  finalSchemaS <- locally server \unwrap -> pure $ mergeSchemas $ hcollapse $ unwrap schemas
+
+  -- Server broadcasts the final schema
+  finalSchema <- (server, finalSchemaS) ~>* clients
+
+  -- Based on the final schema, each client computes what query it wants to run, and sends it back to the server
+  queriesS <- (clients, \c unwrap -> loadQuery c $ unwrap finalSchema) *~~> server
+
+  -- Server broadcasts the list of queries to all clients
+  queries <- (server, \unwrap -> pure $ hcollapse $ unwrap queriesS) ~~>* clients
+
+  -- Each client locally decides if they agree with everyone else's queries, and sends back a response
+  agreements <- (clients, \c unwrap -> agreeWithQueries c $ unwrap queries) *~~> server
+
+  -- Server branches on the responses
+  cond_' (server, \unwrap -> pure $ and $ hcollapse $ unwrap agreements) \case
+    False -> do -- Someone disagrees, end the protocol
+      multilocally clients \_ _ -> pure () -- print "Disagreement"
+      pure $ wrap ()
+    True -> do -- Everyone agrees, proceed
+      -- The amount of times the server will ask for a valid dataset
+      let tries = 5
+
+      -- Clients send their datasets to the server
+      datasets <- getClientDatasets cond_' failures tries finalSchemaS server clients
+
+      -- Server locally merges the datasets and runs all the queries
+      resultsS <- locally server \unwrap -> do
+        let aggregatedDataset = mergeDatasets $ hcollapse $ unwrap datasets
+        hsequenceK $ hmap (mapKK $ runQuery aggregatedDataset) $ unwrap queriesS
+
+      -- Server sends the result of each query "pointwise"
+      results <- (server, resultsS) ~>. clients
+
+      -- Clients consume the received query results
+      multilocally clients \_ unwrap -> pure $ unwrap results
+      pure $ wrap ()
+
+  pure ()
+
+-- Instantiating the choreography
+
+withProxy :: String -> (forall s. KnownSymbol s => Proxy s -> a) -> a
+withProxy s f = withSomeSSymbol s \(ss :: SSymbol s) -> withKnownSymbol ss $ f @s Proxy
+
+withProxies :: [String] -> (forall ss. All KnownSymbol ss => NP Proxy ss -> a) -> a
+withProxies [] f = f Nil
+withProxies (s : ss) f =
+  withProxy s \s ->
+    withProxies ss \ss ->
+      f $ s :* ss
+
+cleanRoomLocs :: Int -> [LocTm]
+cleanRoomLocs numClients = server : clients
+  where
+    server = "s"
+    clients = fmap (\i -> "c" <> show i) [1..numClients]
+
+mkCleanRoom :: Int -> [LocTm] -> Cond_' -> Choreo IO ()
+mkCleanRoom numFailures (server : clients) cond_' =
+  cleanRoom cond_' numFailures & withProxy server & withProxies clients
+mkCleanRoom _ [] _ = error "Empty locs"
diff --git a/bench/Choreos/Kvs.hs b/bench/Choreos/Kvs.hs
new file mode 100644
--- /dev/null
+++ b/bench/Choreos/Kvs.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE BlockArguments      #-}
+{-# LANGUAGE DataKinds           #-}
+{-# LANGUAGE DerivingStrategies  #-}
+{-# LANGUAGE ImpredicativeTypes  #-}
+{-# LANGUAGE LambdaCase          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- This is a version of the `kvs-2-primary-backup` choreography, adapted for the benchmarking setup.
+module Choreos.Kvs where
+
+import Data.Foldable(for_)
+import Data.IORef
+import Data.Map (Map)
+import Data.Map qualified as Map
+import Data.Proxy
+
+import Benchmark
+import Choreography hiding (cond, cond_, cond', cond_')
+import Choreography.Location(wrap)
+
+client :: Proxy "client"
+client = Proxy
+
+primary :: Proxy "primary"
+primary = Proxy
+
+backup :: Proxy "backup"
+backup = Proxy
+
+type State = Map String String
+
+data Request = Put String String | Get String deriving stock (Show, Read)
+
+isPutRequest :: Request -> Bool
+isPutRequest (Put _ _) = True
+isPutRequest _ = False
+
+requests :: [Request]
+requests =
+  [ Put "x" "a"
+  , Put "y" "b"
+  , Get "x"
+  , Put "y" "c"
+  , Get "y"
+  ]
+
+type Response = Maybe String
+
+-- | `handleRequest` handle a request and returns the new the state.
+handleRequest :: Request -> IORef State -> IO Response
+handleRequest request stateRef = case request of
+  Put key value -> do
+    modifyIORef stateRef (Map.insert key value)
+    return (Just value)
+  Get key -> do
+    state <- readIORef stateRef
+    return (Map.lookup key state)
+
+-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
+-- If the request is a `PUT`, it will forward the request to the backup node.
+kvs ::
+  Cond_' ->
+  Request @ "client" ->
+  IORef State @ "primary" ->
+  IORef State @ "backup" ->
+  Choreo IO (Response @ "client")
+kvs cond_' request primaryStateRef backupStateRef = do
+  -- send request to the primary node
+  request' <- (client, request) ~> primary
+
+  -- branch on the request
+  cond_' (primary, \unwrap -> pure $ isPutRequest $ unwrap request') \case
+    -- if the request is a `PUT`, forward the request to the backup node
+    True -> do
+      request'' <- (primary, request') ~> backup
+      ack <-
+        backup `locally` \unwrap -> do
+          handleRequest (unwrap request'') (unwrap backupStateRef)
+      (backup, ack) ~> primary
+      return $ wrap ()
+    _ -> do
+      return $ wrap ()
+
+  -- process request on the primary node
+  response <-
+    primary `locally` \unwrap ->
+      handleRequest (unwrap request') (unwrap primaryStateRef)
+
+  -- send response to client
+  (primary, response) ~> client
+
+kvs' :: Cond_' -> Choreo IO ()
+kvs' cond_' = do
+  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)
+  for_ requests \request -> do
+    -- Replaced the interactive prompt with a dummy `locally` to keep the choreography's structure,
+    -- for testing the overhead of `cond_`'s static analysis.
+    clientRequest <- client `locally` \_ -> pure request
+    kvs cond_' clientRequest primaryStateRef backupStateRef
+    client `locally` \_ -> pure () -- putStrLn ("> " ++ show (unwrap response)) -- Don't print during benchmarking
+    pure ()
diff --git a/bench/Main.hs b/bench/Main.hs
new file mode 100644
--- /dev/null
+++ b/bench/Main.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+
+module Main where
+
+import System.Environment(getArgs)
+import System.Exit(exitFailure)
+
+import Benchmark
+import Choreos.Bookseller
+import Choreos.CleanRoom
+import Choreos.Kvs
+
+main :: IO ()
+main = getArgs >>= \case
+  [backend, cond, "bookseller"] -> benchmarkChoreography backend cond ["buyer", "seller"] bookseller'
+  [backend, cond, "kvs"] -> benchmarkChoreography backend cond ["client", "primary", "backup"] kvs'
+  [backend, cond, "clean-room", read -> numClients, read -> numFailures] ->
+    let locs = cleanRoomLocs numClients
+    in benchmarkChoreography backend cond locs $ mkCleanRoom numFailures locs
+  _ -> putStrLn "Invalid args" *> exitFailure
diff --git a/cloudchor.cabal b/cloudchor.cabal
new file mode 100644
--- /dev/null
+++ b/cloudchor.cabal
@@ -0,0 +1,290 @@
+cabal-version: 3.8
+
+name: cloudchor
+version: 0.1.0.0
+synopsis: Lightweight and efficient choreographic programming for cloud services
+description: Please see the README on GitHub at <https://github.com/aionescu/cloudchor#readme>
+category: Concurrency
+
+homepage: https://github.com/aionescu/cloudchor
+bug-reports: https://github.com/aionescu/cloudchor/issues
+
+author: Alex Ionescu
+maintainer: Alex Ionescu <hackage@ionescu.sh>
+copyright: Copyright (C) 2025 Alex Ionescu
+
+license: MPL-2.0
+license-files:
+  LICENSE.txt
+  LICENSE-HasChor.txt
+
+build-type: Simple
+extra-doc-files: README.md
+
+tested-with:
+  GHC == 9.10.3
+  GHC == 9.8.4
+
+source-repository head
+  type: git
+  location: https://github.com/aionescu/cloudchor
+
+common threaded
+  ghc-options:
+    -threaded
+    -rtsopts
+    -with-rtsopts=-N
+
+common warnings
+  ghc-options:
+    -Wall
+    -Wcompat
+    -Widentities
+    -Wmissing-deriving-strategies
+    -Wno-name-shadowing
+    -Wno-unused-do-bind
+    -Wno-type-defaults
+    -Wpartial-fields
+    -Wprepositive-qualified-module
+    -Wredundant-constraints
+    -Wunused-packages
+
+library
+  import: warnings
+  default-language: GHC2021
+  hs-source-dirs: src
+
+  exposed-modules:
+    Choreography
+    Choreography.Choreo
+    Choreography.Location
+    Choreography.Location.Multi
+    Choreography.Network
+    Choreography.Network.Http
+    Choreography.Network.Local
+    Choreography.Network.LocalMsgCount
+    Control.Monad.Freer
+
+  build-depends:
+    base                   >= 4.16 && < 4.21
+    , containers           >= 0.6  && < 0.7
+    , http-client          >= 0.7  && < 0.8
+    , mtl                  >= 2.3  && < 2.4
+    , servant              >= 0.19 && < 0.21
+    , servant-client       >= 0.19 && < 0.21
+    , servant-server       >= 0.19 && < 0.21
+    , sop-core             >= 0.5  && < 0.6
+    , template-haskell     >= 2.18 && < 2.23
+    , unordered-containers >= 0.2  && < 0.3
+    , warp                 >= 3.3  && < 3.4
+
+executable bench
+  import: threaded, warnings
+  default-language: GHC2021
+  hs-source-dirs: bench
+  main-is: Main.hs
+
+  other-modules:
+    Benchmark
+    Choreos.Bookseller
+    Choreos.CleanRoom
+    Choreos.Kvs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , sop-core   >= 0.5  && < 0.6
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-clean-room
+  import: warnings
+  default-language: GHC2021
+  hs-source-dirs: examples/clean-room
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , random     >= 1.2  && < 1.3
+    , sop-core   >= 0.5  && < 0.6
+    , cloudchor
+
+executable example-unsoundness
+  import: warnings
+  default-language: GHC2021
+  hs-source-dirs: examples/unsoundness
+  main-is: Main.hs
+
+  build-depends:
+    base             >= 4.16 && < 4.21
+    , http-client    >= 0.7  && < 0.8
+    , servant        >= 0.19 && < 0.21
+    , servant-client >= 0.19 && < 0.21
+    , servant-server >= 0.19 && < 0.21
+    , warp           >= 3.3  && < 3.4
+    , cloudchor
+
+executable example-HasChor-bank-2pc
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/bank-2pc
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , split     >= 0.2  && < 0.3
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-bookseller-0-network
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/bookseller-0-network
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-bookseller-1-simple
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/bookseller-1-simple
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-bookseller-2-higher-order
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/bookseller-2-higher-order
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-bookseller-3-loc-poly
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/bookseller-3-loc-poly
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-diffiehellman
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/diffiehellman
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , random    >= 1.2  && < 1.3
+    , time      >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-karatsuba
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/karatsuba
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , async      >= 2.2  && < 2.3
+    , containers >= 0.6  && < 0.7
+    , cloudchor
+
+executable example-HasChor-kvs1
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/kvs-1-simple
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-kvs2
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/kvs-2-primary-backup
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-kvs3
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/kvs-3-higher-order
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-kvs4
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/kvs-4-loc-poly
+  main-is: Main.hs
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-mergesort
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/mergesort
+  main-is: Main.hs
+
+  ghc-options:
+    -Wno-x-partial
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-quicksort
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/quicksort
+  main-is: Main.hs
+
+  ghc-options:
+    -Wno-x-partial
+
+  build-depends:
+    base         >= 4.16 && < 4.21
+    , async      >= 2.2  && < 2.3
+    , containers >= 0.6  && < 0.7
+    , time       >= 1.11 && < 1.13
+    , cloudchor
+
+executable example-HasChor-ring-leader
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/ring-leader
+  main-is: Main.hs
+
+  build-depends:
+    base           >= 4.16 && < 4.21
+    , transformers >= 0.5  && < 0.7
+    , cloudchor
+
+executable example-HasChor-playground
+  default-language: GHC2021
+  hs-source-dirs: examples/HasChor/playground
+  main-is: Main.hs
+
+  build-depends:
+    base        >= 4.16 && < 4.21
+    , cloudchor
diff --git a/examples/HasChor/bank-2pc/Main.hs b/examples/HasChor/bank-2pc/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/bank-2pc/Main.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Data.List.Split (splitOn)
+import Data.Maybe (catMaybes, mapMaybe)
+import Data.Proxy
+import System.Environment
+import Text.Read (readMaybe)
+
+client :: Proxy "client"
+client = Proxy
+
+coordinator :: Proxy "coordinator"
+coordinator = Proxy
+
+alice :: Proxy "alice"
+alice = Proxy
+
+bob :: Proxy "bob"
+bob = Proxy
+
+type State = (Int @ "alice", Int @ "bob")
+
+type Action = (String, Int)
+
+type Transaction = [Action]
+
+-- | `validate` checks if a transaction can be executed while keeping balance >= 0
+-- returns if the transaction satisfies the property and the balance after the transaction
+validate :: String -> Int -> Transaction -> (Bool, Int)
+validate name balance tx = foldl (\(valid, i) (_, amount) -> (let next = i + amount in (valid && next >= 0, next))) (True, balance) actions
+  where
+    actions = filter (\(n, _) -> n == name) tx
+
+-- | `parse` converts the user input into a transaction
+parse :: String -> Transaction
+parse s = tx
+  where
+    t = splitOn ";" s
+    f :: String -> Maybe Action
+    f l = do
+      [target, amountStr] <- return $ words l
+      amount <- readMaybe amountStr :: Maybe Int
+      target' <- if target == "alice" || target == "bob" then Just target else Nothing
+      return (target', amount)
+    tx = mapMaybe f t
+
+-- | `handleTransaction` is a choreography that handles a transaction.
+-- Given the current state and a transaction, it will first ask alice and bob to vote,
+-- then it will decide whether to commit the transaction or not.
+-- If the transaction is committed, it will update the state.
+-- Otherwise, it will keep the state unchanged.
+handleTransaction :: State -> Transaction @ "coordinator" -> Choreo IO (Bool @ "coordinator", State)
+handleTransaction (aliceBalance, bobBalance) tx = do
+  -- Voting Phase
+  txa <- (coordinator, tx) ~> alice
+  voteAlice <- (alice, \unwrap -> do { return $ fst $ validate "alice" (unwrap aliceBalance) (unwrap txa) }) ~~> coordinator
+  txb <- (coordinator, tx) ~> bob
+  voteBob <- (bob, \unwrap -> do { return $ fst $ validate "bob" (unwrap bobBalance) (unwrap txb) }) ~~> coordinator
+
+  -- Check if the transaction can be committed
+  canCommit <- coordinator `locally` \unwrap -> do return $ unwrap voteAlice && unwrap voteBob
+
+  -- Commit Phase
+  cond (coordinator, canCommit) \case
+    True -> do
+      aliceBalance' <- alice `locally` \unwrap -> do return $ snd $ validate "alice" (unwrap aliceBalance) (unwrap txa)
+      bobBalance' <- bob `locally` \unwrap -> do return $ snd $ validate "bob" (unwrap bobBalance) (unwrap txb)
+      return (canCommit, (aliceBalance', bobBalance'))
+    False -> do
+      return (canCommit, (aliceBalance, bobBalance))
+
+-- | `bank` loops forever and handles transactions.
+bank :: State -> Choreo IO ()
+bank state = do
+  client `locally` \_ -> do
+    putStrLn "Command? (alice|bob {amount};)+"
+  tx <- (client, \_ -> do { parse <$> getLine }) ~~> coordinator
+  (committed, state') <- handleTransaction state tx
+  committed' <- (coordinator, committed) ~> client
+  client `locally` \unwrap -> do
+    putStrLn if unwrap committed' then "Committed" else "Not committed"
+  alice `locally` \unwrap -> do putStrLn ("Alice's balance: " ++ show (unwrap (fst state')))
+  bob `locally` \unwrap -> do putStrLn ("Bob's balance: " ++ show (unwrap (snd state')))
+  bank state' -- repeat
+  return ()
+
+-- | `startBank` is a choreography that initializes the states and starts the bank application.
+startBank :: Choreo IO ()
+startBank = do
+  aliceBalance <- alice `locally` \_ -> do return 0
+  bobBalance <- bob `locally` \_ -> do return 0
+  bank (aliceBalance, bobBalance)
+
+main :: IO ()
+main = do
+  runChoreo startBank
diff --git a/examples/HasChor/bookseller-0-network/Main.hs b/examples/HasChor/bookseller-0-network/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/bookseller-0-network/Main.hs
@@ -0,0 +1,55 @@
+module Main where
+
+import Choreography.Network
+import Choreography.Network.Http
+import Data.Time
+import System.Environment
+
+buyer :: Network IO ()
+buyer = do
+  run $ putStrLn "Enter the title of the book to buy:"
+  title <- run getLine
+  send title "seller"
+  price <- recv "seller"
+  if price < budget
+  then do
+    send True "seller"
+    (deliveryDate :: Day) <- recv "seller"
+    run $ putStrLn ("The book will be delivered on " ++ (show deliveryDate))
+  else do
+    send False "seller"
+    run $ putStrLn "The book's price is out of the budget"
+
+seller :: Network IO ()
+seller = do
+  title <- recv "buyer"
+  send (priceOf title) "buyer"
+  decision <- recv "buyer"
+  if decision
+  then do
+    send (deliveryDateOf title) "buyer"
+  else do
+    return ()
+
+budget :: Int
+budget = 100
+
+priceOf :: String -> Int
+priceOf "Types and Programming Languages" = 80
+priceOf "Homotopy Type Theory"            = 120
+
+deliveryDateOf :: String -> Day
+deliveryDateOf "Types and Programming Languages" = fromGregorian 2023 12 19
+deliveryDateOf "Homotopy Type Theory"            = fromGregorian 2023 09 18
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "buyer" -> runNetwork cfg "buyer" buyer
+    "seller" -> runNetwork cfg "seller" seller
+  return ()
+  where
+    cfg = mkHttpConfig [ ("buyer",  ("localhost", 4242))
+                       , ("seller", ("localhost", 4343))
+                       ]
diff --git a/examples/HasChor/bookseller-1-simple/Main.hs b/examples/HasChor/bookseller-1-simple/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/bookseller-1-simple/Main.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds      #-}
+{-# LANGUAGE LambdaCase     #-}
+
+module Main where
+
+import Choreography
+import Data.Proxy
+import Data.Time
+import System.Environment
+
+buyer :: Proxy "buyer"
+buyer = Proxy
+
+seller :: Proxy "seller"
+seller = Proxy
+
+-- | `bookseller` is a choreography that implements the bookseller protocol.
+bookseller :: Choreo IO (Maybe Day @ "buyer")
+bookseller = do
+  -- the buyer node prompts the user to enter the title of the book to buy
+  title <-
+    buyer `locally` \_ -> do
+      putStrLn "Enter the title of the book to buy"
+      getLine
+  -- the buyer sends the title to the seller
+  title' <- (buyer, title) ~> seller
+
+  -- the seller checks the price of the book
+  price <- seller `locally` \un -> return $ priceOf (un title')
+  -- the seller sends back the price of the book to the buyer
+  price' <- (seller, price) ~> buyer
+
+  -- the buyer decides whether to buy the book or not
+  decision <- buyer `locally` \un -> return $ (un price') < budget
+
+  -- if the buyer decides to buy the book, the seller sends the delivery date to the buyer
+  cond (buyer, decision) \case
+    True  -> do
+      deliveryDate  <- seller `locally` \un -> return $ deliveryDateOf (un title')
+      deliveryDate' <- (seller, deliveryDate) ~> buyer
+
+      buyer `locally` \un -> do
+        putStrLn $ "The book will be delivered on " ++ show (un deliveryDate')
+        return $ Just (un deliveryDate')
+
+    False -> do
+      buyer `locally` \_ -> do
+        putStrLn "The book's price is out of the budget"
+        return Nothing
+
+-- `bookseller'` is a simplified version of `bookseller` that utilizes `~~>`
+bookseller' :: Choreo IO (Maybe Day @ "buyer")
+bookseller' = do
+  title <- (buyer, \_ -> do
+               putStrLn "Enter the title of the book to buy"
+               getLine
+           )
+           ~~> seller
+
+  price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer
+
+  cond' (buyer, \un -> return $ (un price) < budget) \case
+    True  -> do
+      deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer
+
+      buyer `locally` \un -> do
+        putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)
+        return $ Just (un deliveryDate)
+
+    False -> do
+      buyer `locally` \_ -> do
+        putStrLn "The book's price is out of the budget"
+        return Nothing
+
+budget :: Int
+budget = 100
+
+priceOf :: String -> Int
+priceOf "Types and Programming Languages" = 80
+priceOf "Homotopy Type Theory"            = 120
+
+deliveryDateOf :: String -> Day
+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19
+deliveryDateOf "Homotopy Type Theory"            = fromGregorian 2023 01 01
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "buyer"  -> runChoreography cfg bookseller' "buyer"
+    "seller" -> runChoreography cfg bookseller' "seller"
+  return ()
+  where
+    cfg = mkHttpConfig [ ("buyer",  ("localhost", 4242))
+                       , ("seller", ("localhost", 4343))
+                       ]
diff --git a/examples/HasChor/bookseller-2-higher-order/Main.hs b/examples/HasChor/bookseller-2-higher-order/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/bookseller-2-higher-order/Main.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds      #-}
+{-# LANGUAGE LambdaCase     #-}
+
+module Main where
+
+import Choreography
+import Data.Proxy
+import Data.Time
+import System.Environment
+
+buyer :: Proxy "buyer"
+buyer = Proxy
+
+seller :: Proxy "seller"
+seller = Proxy
+
+buyer2 :: Proxy "buyer2"
+buyer2 = Proxy
+
+-- | `bookseller` is a choreography that implements the bookseller protocol.
+-- This version takes a choreography `mkDecision` that implements the decision making process.
+bookseller :: (Int @ "buyer" -> Choreo IO (Bool @ "buyer")) -> Choreo IO (Maybe Day @ "buyer")
+bookseller mkDecision = do
+  -- the buyer reads the title of the book and sends it to the seller
+  title <- (buyer, \_ -> do
+               putStrLn "Enter the title of the book to buy"
+               getLine
+           )
+           ~~> seller
+
+  -- the seller checks the price of the book and sends it to the buyer
+  price <- (seller, \un -> return $ priceOf (un title)) ~~> buyer
+
+  -- the buyer makes a decision using the `mkDecision` choreography
+  decision <- mkDecision price
+
+  -- if the buyer decides to buy the book, the seller sends the delivery date to the buyer
+  cond (buyer, decision) \case
+    True  -> do
+      deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> buyer
+
+      buyer `locally` \un -> do
+        putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)
+        return $ Just (un deliveryDate)
+
+    False -> do
+      buyer `locally` \_ -> do
+        putStrLn "The book's price is out of the budget"
+        return Nothing
+
+-- | `mkDecision1` checks if buyer's budget is greater than the price of the book
+mkDecision1 :: Int @ "buyer" -> Choreo IO (Bool @ "buyer")
+mkDecision1 price = do
+  buyer `locally` \un -> return $ un price < budget
+
+-- | `mkDecision2` asks buyer2 how much they're willing to contribute and checks
+-- if the buyer's budget is greater than the price of the book minus buyer2's contribution
+mkDecision2 :: Int @ "buyer" -> Choreo IO (Bool @ "buyer")
+mkDecision2 price = do
+  contrib <- (buyer2, \_ -> do
+                 putStrLn "How much you're willing to contribute?"
+                 read <$> getLine
+             )
+             ~~> buyer
+  buyer `locally` \un -> return $ un price - un contrib <= budget
+
+budget :: Int
+budget = 100
+
+priceOf :: String -> Int
+priceOf "Types and Programming Languages" = 80
+priceOf "Homotopy Type Theory"            = 120
+
+deliveryDateOf :: String -> Day
+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19
+deliveryDateOf "Homotopy Type Theory"            = fromGregorian 2023 01 01
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "buyer"  -> runChoreography cfg choreo "buyer"
+    "seller" -> runChoreography cfg choreo "seller"
+    "buyer2" -> runChoreography cfg choreo "buyer2"
+  return ()
+  where
+    choreo = bookseller mkDecision2
+
+    cfg = mkHttpConfig [ ("buyer",  ("localhost", 4242))
+                       , ("seller", ("localhost", 4343))
+                       , ("buyer2", ("localhost", 4444))
+                       ]
diff --git a/examples/HasChor/bookseller-3-loc-poly/Main.hs b/examples/HasChor/bookseller-3-loc-poly/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/bookseller-3-loc-poly/Main.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds      #-}
+{-# LANGUAGE LambdaCase     #-}
+
+module Main where
+
+import Choreography
+import Data.Proxy
+import Data.Time
+import GHC.TypeLits
+import System.Environment
+
+buyer :: Proxy "buyer"
+buyer = Proxy
+
+seller :: Proxy "seller"
+seller = Proxy
+
+-- | `bookseller` is a choreography that implements the bookseller protocol.
+-- This version takes the name of the buyer as a parameter (`someBuyer`).
+bookseller :: KnownSymbol a => Proxy a -> Choreo IO (Maybe Day @ a)
+bookseller someBuyer = do
+  -- the buyer reads the title of the book and sends it to the seller
+  title <- (buyer, \_ -> do
+               putStrLn "Enter the title of the book to buy"
+               getLine
+           )
+           ~~> seller
+
+  -- the seller checks the price of the book and sends it to the buyer
+  price <- (seller, \un -> return $ priceOf (un title)) ~~> someBuyer
+
+  cond' (someBuyer, \un -> return $ (un price) < budget) \case
+    True  -> do
+      deliveryDate <- (seller, \un -> return $ deliveryDateOf (un title)) ~~> someBuyer
+
+      someBuyer `locally` \un -> do
+        putStrLn $ "The book will be delivered on " ++ show (un deliveryDate)
+        return $ Just (un deliveryDate)
+
+    False -> do
+      someBuyer `locally` \_ -> do
+        putStrLn "The book's price is out of the budget"
+        return Nothing
+
+budget :: Int
+budget = 100
+
+priceOf :: String -> Int
+priceOf "Types and Programming Languages" = 80
+priceOf "Homotopy Type Theory"            = 120
+
+deliveryDateOf :: String -> Day
+deliveryDateOf "Types and Programming Languages" = fromGregorian 2022 12 19
+deliveryDateOf "Homotopy Type Theory"            = fromGregorian 2023 01 01
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "buyer"  -> runChoreography cfg choreo "buyer"
+    "seller" -> runChoreography cfg choreo "seller"
+  return ()
+  where
+    choreo = bookseller buyer
+    
+    cfg = mkHttpConfig [ ("buyer",  ("localhost", 4242))
+                       , ("seller", ("localhost", 4343))
+                       ]
diff --git a/examples/HasChor/diffiehellman/Main.hs b/examples/HasChor/diffiehellman/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/diffiehellman/Main.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Main where
+
+import Choreography (mkHttpConfig, runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Data.Proxy
+import Data.Time
+import System.Environment
+import System.Random
+
+-- helper functions around prime number
+-- https://nulldereference.wordpress.com/2012/02/04/generating-prime-numbers-with-haskell/
+divisors :: Integer -> [Integer]
+divisors 1 = [1]
+divisors x = 1 : [y | y <- [2 .. (x `div` 2)], x `mod` y == 0] ++ [x]
+
+isPrime :: Integer -> Bool
+isPrime x = divisors x == [1, x]
+
+primeNums :: [Integer]
+primeNums = [x | x <- [2 ..], isPrime x]
+
+-- set up proxies
+alice :: Proxy "alice"
+alice = Proxy
+
+bob :: Proxy "bob"
+bob = Proxy
+
+diffieHellman :: Choreo IO (Integer @ "alice", Integer @ "bob")
+diffieHellman = do
+  -- wait for alice to initiate the process
+  alice `locally` \unwrap -> do
+    putStrLn "enter to start key exchange..."
+    getLine
+  bob `locally` \unwrap -> do
+    putStrLn "waiting for alice to initiate key exchange"
+
+  -- alice picks p and g and sends them to bob
+  pa <-
+    alice `locally` \unwrap -> do
+      x <- randomRIO (200, 1000 :: Int)
+      return $ primeNums !! x
+  pb <- (alice, pa) ~> bob
+  ga <- alice `locally` \unwrap -> do randomRIO (10, unwrap pa)
+  gb <- (alice, ga) ~> bob
+
+  -- alice and bob select secrets
+  a <- alice `locally` \unwrap -> do randomRIO (200, 1000 :: Integer)
+  b <- bob `locally` \unwrap -> do randomRIO (200, 1000 :: Integer)
+
+  -- alice and bob computes numbers that they exchange
+  a' <- alice `locally` \unwrap -> do return $ unwrap ga ^ unwrap a `mod` unwrap pa
+  b' <- bob `locally` \unwrap -> do return $ unwrap gb ^ unwrap b `mod` unwrap pb
+
+  -- exchange numbers
+  a'' <- (alice, a') ~> bob
+  b'' <- (bob, b') ~> alice
+
+  -- compute shared key
+  s1 <-
+    alice `locally` \unwrap ->
+      let s = unwrap b'' ^ unwrap a `mod` unwrap pa
+       in do
+            putStrLn ("alice's shared key: " ++ show s)
+            return s
+  s2 <-
+    bob `locally` \unwrap ->
+      let s = unwrap a'' ^ unwrap b `mod` unwrap pb
+       in do
+            putStrLn ("bob's shared key: " ++ show s)
+            return s
+  return (s1, s2)
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  x <- case loc of
+    "alice" -> runChoreography config diffieHellman "alice"
+    "bob" -> runChoreography config diffieHellman "bob"
+  return ()
+  where
+    config =
+      mkHttpConfig
+        [ ("alice", ("localhost", 5000)),
+          ("bob", ("localhost", 5001))
+        ]
diff --git a/examples/HasChor/karatsuba/Main.hs b/examples/HasChor/karatsuba/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/karatsuba/Main.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Local
+import Control.Concurrent.Async (async, mapConcurrently_, wait)
+import Data.Proxy
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+primary :: Proxy "primary"
+primary = Proxy
+
+worker1 :: Proxy "worker1"
+worker1 = Proxy
+
+worker2 :: Proxy "worker2"
+worker2 = Proxy
+
+data KaratsubaNums = KaratsubaNums
+  { splitter :: Integer,
+    h1 :: Integer,
+    h2 :: Integer,
+    l1 :: Integer,
+    l2 :: Integer
+  }
+
+karatsuba ::
+  (KnownSymbol a, KnownSymbol b, KnownSymbol c) =>
+  Proxy a ->
+  Proxy b ->
+  Proxy c ->
+  (Integer @ a) ->
+  (Integer @ a) ->
+  Choreo IO (Integer @ a)
+karatsuba a b c n1 n2 = do
+  done <- a `locally` \unwrap -> return $ unwrap n1 < 10 || unwrap n2 < 10
+  cond
+    (a, done)
+    \case
+      True -> do
+        a `locally` \unwrap -> return $ unwrap n1 * unwrap n2
+      False -> do
+        x <- a `locally` \unwrap -> return $ f (unwrap n1) (unwrap n2)
+        l1' <- (a, \unwrap -> return $ l1 (unwrap x)) ~~> b
+        l2' <- (a, \unwrap -> return $ l2 (unwrap x)) ~~> b
+        h1' <- (a, \unwrap -> return $ h1 (unwrap x)) ~~> c
+        h2' <- (a, \unwrap -> return $ h2 (unwrap x)) ~~> c
+        z0' <- karatsuba b c a l1' l2'
+        z0 <- (b, z0') ~> a
+        z2' <- karatsuba c a b h1' h2'
+        z2 <- (c, z2') ~> a
+        s1 <- a `locally` \unwrap -> return $ l1 (unwrap x) + h1 (unwrap x)
+        s2 <- a `locally` \unwrap -> return $ l2 (unwrap x) + h2 (unwrap x)
+        z1' <- karatsuba a b c s1 s2
+        z1 <- a `locally` \unwrap -> return $ unwrap z1' - unwrap z2 - unwrap z0
+        a `locally` \unwrap -> return let s = splitter (unwrap x) in (unwrap z2 * s * s) + (unwrap z1 * s) + unwrap z0
+        where
+          f n1 n2 = KaratsubaNums {splitter = splitter, h1 = h1, l1 = l1, h2 = h2, l2 = l2}
+            where
+              log10 :: Integer -> Double
+              log10 = logBase 10 . fromIntegral
+              m = max (log10 n1) (log10 n2) + 1
+              m2 = floor (m / 2)
+              splitter = 10 ^ m2
+              h1 = n1 `div` splitter
+              l1 = n1 `mod` splitter
+              h2 = n2 `div` splitter
+              l2 = n2 `mod` splitter
+
+mainChoreo :: Integer -> Integer -> Choreo IO ()
+mainChoreo n1 n2 = do
+  n1 <- primary `locally` \_ -> return n1
+  n2 <- primary `locally` \_ -> return n2
+  result <- karatsuba primary worker1 worker2 n1 n2
+  primary `locally` \unwrap -> do
+    print (unwrap result)
+    return ()
+  return ()
+
+main :: IO ()
+main = do
+  [n1, n2] <- map read <$> getArgs
+  config <- mkLocalConfig locs
+  mapConcurrently_ (runChoreography config (mainChoreo n1 n2)) locs
+  return ()
+  where
+    locs = ["primary", "worker1", "worker2"]
diff --git a/examples/HasChor/kvs-1-simple/Main.hs b/examples/HasChor/kvs-1-simple/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/kvs-1-simple/Main.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Control.Concurrent (threadDelay)
+import Control.Monad
+import Data.IORef
+import Data.Map (Map, (!))
+import Data.Map qualified as Map
+import Data.Maybe (fromMaybe, isJust)
+import Data.Proxy
+import GHC.IORef (IORef (IORef))
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+client :: Proxy "client"
+client = Proxy
+
+server :: Proxy "server"
+server = Proxy
+
+type State = Map String String
+
+data Request = Put String String | Get String deriving (Show, Read)
+
+type Response = Maybe String
+
+-- | `readRequest` reads a request from the terminal.
+readRequest :: IO Request
+readRequest = do
+  putStrLn "Command?"
+  line <- getLine
+  case parseRequest line of
+    Just t -> return t
+    Nothing -> putStrLn "Invalid command" >> readRequest
+  where
+    parseRequest :: String -> Maybe Request
+    parseRequest s =
+      let l = words s
+       in case l of
+            ["GET", k] -> Just (Get k)
+            ["PUT", k, v] -> Just (Put k v)
+            _ -> Nothing
+
+-- | `handleRequest` handle a request and returns the new the state.
+handleRequest :: Request -> IORef State -> IO Response
+handleRequest request stateRef = case request of
+  Put key value -> do
+    modifyIORef stateRef (Map.insert key value)
+    return (Just value)
+  Get key -> do
+    state <- readIORef stateRef
+    return (Map.lookup key state)
+
+-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
+kvs ::
+  Request @ "client" ->
+  IORef State @ "server" ->
+  Choreo IO (Response @ "client")
+kvs request stateRef = do
+  -- send the request to the server
+  request' <- (client, request) ~> server
+  -- the server handles the response and creates a response
+  response <-
+    server `locally` \unwrap ->
+      handleRequest (unwrap request') (unwrap stateRef)
+  -- send the response back to the client
+  (server, response) ~> client
+
+-- | `mainChoreo` is a choreography that serves as the entry point of the program.
+-- It initializes the state and loops forever.
+mainChoreo :: Choreo IO ()
+mainChoreo = do
+  stateRef <- server `locally` \_ -> newIORef (Map.empty :: State)
+  loop stateRef
+  where
+    loop :: IORef State @ "server" -> Choreo IO ()
+    loop stateRef = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRef
+      client `locally` \unwrap -> do putStrLn ("> " ++ (show (unwrap response)))
+      loop stateRef
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "client" -> runChoreography config mainChoreo "client"
+    "server" -> runChoreography config mainChoreo "server"
+  return ()
+  where
+    config =
+      mkHttpConfig
+        [ ("client", ("localhost", 3000)),
+          ("server", ("localhost", 4000))
+        ]
diff --git a/examples/HasChor/kvs-2-primary-backup/Main.hs b/examples/HasChor/kvs-2-primary-backup/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/kvs-2-primary-backup/Main.hs
@@ -0,0 +1,125 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Control.Concurrent (threadDelay)
+import Control.Monad
+import Data.IORef
+import Data.Map (Map, (!))
+import Data.Map qualified as Map
+import Data.Maybe (fromMaybe, isJust)
+import Data.Proxy
+import GHC.IORef (IORef (IORef))
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+client :: Proxy "client"
+client = Proxy
+
+primary :: Proxy "primary"
+primary = Proxy
+
+backup :: Proxy "backup"
+backup = Proxy
+
+type State = Map String String
+
+data Request = Put String String | Get String deriving (Show, Read)
+
+type Response = Maybe String
+
+-- | `readRequest` reads a request from the terminal.
+readRequest :: IO Request
+readRequest = do
+  putStrLn "Command?"
+  line <- getLine
+  case parseRequest line of
+    Just t -> return t
+    Nothing -> putStrLn "Invalid command" >> readRequest
+  where
+    parseRequest :: String -> Maybe Request
+    parseRequest s =
+      let l = words s
+       in case l of
+            ["GET", k] -> Just (Get k)
+            ["PUT", k, v] -> Just (Put k v)
+            _ -> Nothing
+
+-- | `handleRequest` handle a request and returns the new the state.
+handleRequest :: Request -> IORef State -> IO Response
+handleRequest request stateRef = case request of
+  Put key value -> do
+    modifyIORef stateRef (Map.insert key value)
+    return (Just value)
+  Get key -> do
+    state <- readIORef stateRef
+    return (Map.lookup key state)
+
+-- | `kvs` is a choreography that processes a single request located at the client and returns the response.
+-- If the request is a `PUT`, it will forward the request to the backup node.
+kvs ::
+  Request @ "client" ->
+  (IORef State @ "primary", IORef State @ "backup") ->
+  Choreo IO (Response @ "client")
+kvs request (primaryStateRef, backupStateRef) = do
+  -- send request to the primary node
+  request' <- (client, request) ~> primary
+
+  -- branch on the request
+  cond (primary, request') \case
+    -- if the request is a `PUT`, forward the request to the backup node
+    Put key value -> do
+      request'' <- (primary, request') ~> backup
+      ack <-
+        backup `locally` \unwrap -> do
+          handleRequest (unwrap request'') (unwrap backupStateRef)
+      (backup, ack) ~> primary
+      return ()
+    _ -> do
+      return ()
+
+  -- process request on the primary node
+  response <-
+    primary `locally` \unwrap ->
+      handleRequest (unwrap request') (unwrap primaryStateRef)
+
+  -- send response to client
+  (primary, response) ~> client
+
+-- | `mainChoreo` is a choreography that serves as the entry point of the program.
+-- It initializes the state and loops forever.
+mainChoreo :: Choreo IO ()
+mainChoreo = do
+  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)
+  loop (primaryStateRef, backupStateRef)
+  where
+    loop :: (IORef State @ "primary", IORef State @ "backup") -> Choreo IO ()
+    loop stateRefs = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRefs
+      client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))
+      loop stateRefs
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "client" -> runChoreography config mainChoreo "client"
+    "primary" -> runChoreography config mainChoreo "primary"
+    "backup" -> runChoreography config mainChoreo "backup"
+  return ()
+  where
+    config =
+      mkHttpConfig
+        [ ("client", ("localhost", 3000)),
+          ("primary", ("localhost", 4000)),
+          ("backup", ("localhost", 5000))
+        ]
diff --git a/examples/HasChor/kvs-3-higher-order/Main.hs b/examples/HasChor/kvs-3-higher-order/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/kvs-3-higher-order/Main.hs
@@ -0,0 +1,156 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Control.Concurrent (threadDelay)
+import Control.Monad
+import Data.IORef
+import Data.Map (Map, (!))
+import Data.Map qualified as Map
+import Data.Maybe (fromMaybe, isJust)
+import Data.Proxy
+import GHC.IORef (IORef (IORef))
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+client :: Proxy "client"
+client = Proxy
+
+primary :: Proxy "primary"
+primary = Proxy
+
+backup :: Proxy "backup"
+backup = Proxy
+
+type State = Map String String
+
+data Request = Put String String | Get String deriving (Show, Read)
+
+type Response = Maybe String
+
+-- | `readRequest` reads a request from the terminal.
+readRequest :: IO Request
+readRequest = do
+  putStrLn "Command?"
+  line <- getLine
+  case parseRequest line of
+    Just t -> return t
+    Nothing -> putStrLn "Invalid command" >> readRequest
+  where
+    parseRequest :: String -> Maybe Request
+    parseRequest s =
+      let l = words s
+       in case l of
+            ["GET", k] -> Just (Get k)
+            ["PUT", k, v] -> Just (Put k v)
+            _ -> Nothing
+
+-- | `handleRequest` handle a request and returns the new the state.
+handleRequest :: Request -> IORef State -> IO Response
+handleRequest request stateRef = case request of
+  Put key value -> do
+    modifyIORef stateRef (Map.insert key value)
+    return (Just value)
+  Get key -> do
+    state <- readIORef stateRef
+    return (Map.lookup key state)
+
+-- | ReplicationStrategy specifies how a request should be handled on possibly replicated servers
+-- `a` is a type that represent states across locations
+type ReplicationStrategy a =
+  Request @ "primary" -> a -> Choreo IO (Response @ "primary")
+
+-- | `nullReplicationStrategy` is a replication strategy that does not replicate the state.
+nullReplicationStrategy :: ReplicationStrategy (IORef State @ "primary")
+nullReplicationStrategy request stateRef = do
+  primary `locally` \unwrap ->
+    handleRequest (unwrap request) (unwrap stateRef)
+
+-- | `primaryBackupReplicationStrategy` is a replication strategy that replicates the state to a backup server.
+primaryBackupReplicationStrategy ::
+  ReplicationStrategy (IORef State @ "primary", IORef State @ "backup")
+primaryBackupReplicationStrategy request (primaryStateRef, backupStateRef) = do
+  -- relay request to backup if it is mutating (= PUT)
+  cond (primary, request) \case
+    Put _ _ -> do
+      request' <- (primary, request) ~> backup
+      ( backup,
+        \unwrap ->
+          handleRequest (unwrap request') (unwrap backupStateRef)
+        )
+        ~~> primary
+      return ()
+    _ -> do
+      return ()
+
+  -- process request on primary
+  primary `locally` \unwrap ->
+    handleRequest (unwrap request) (unwrap primaryStateRef)
+
+-- | `kvs` is a choreography that processes a single request at the client and returns the response.
+-- It uses the provided replication strategy to handle the request.
+kvs ::
+  forall a.
+  Request @ "client" ->
+  a ->
+  ReplicationStrategy a ->
+  Choreo IO (Response @ "client")
+kvs request stateRefs replicationStrategy = do
+  request' <- (client, request) ~> primary
+
+  -- call the provided replication strategy
+  response <- replicationStrategy request' stateRefs
+
+  -- send response to client
+  (primary, response) ~> client
+
+-- | `nullReplicationChoreo` is a choreography that uses `nullReplicationStrategy`.
+nullReplicationChoreo :: Choreo IO ()
+nullReplicationChoreo = do
+  stateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  loop stateRef
+  where
+    loop :: IORef State @ "primary" -> Choreo IO ()
+    loop stateRef = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRef nullReplicationStrategy
+      client `locally` \unwrap -> do putStrLn (show (unwrap response))
+      loop stateRef
+
+-- | `primaryBackupChoreo` is a choreography that uses `primaryBackupReplicationStrategy`.
+primaryBackupChoreo :: Choreo IO ()
+primaryBackupChoreo = do
+  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  backupStateRef <- backup `locally` \_ -> newIORef (Map.empty :: State)
+  loop (primaryStateRef, backupStateRef)
+  where
+    loop :: (IORef State @ "primary", IORef State @ "backup") -> Choreo IO ()
+    loop stateRefs = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRefs primaryBackupReplicationStrategy
+      client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))
+      loop stateRefs
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "client" -> runChoreography config mainChoreo "client"
+    "primary" -> runChoreography config mainChoreo "primary"
+    "backup" -> runChoreography config mainChoreo "backup"
+  return ()
+  where
+    mainChoreo = primaryBackupChoreo -- or `nullReplicationChoreo`
+    config =
+      mkHttpConfig
+        [ ("client", ("localhost", 3000)),
+          ("primary", ("localhost", 4000)),
+          ("backup", ("localhost", 5000))
+        ]
diff --git a/examples/HasChor/kvs-4-loc-poly/Main.hs b/examples/HasChor/kvs-4-loc-poly/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/kvs-4-loc-poly/Main.hs
@@ -0,0 +1,196 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Control.Concurrent (threadDelay)
+import Control.Monad
+import Data.IORef
+import Data.Map (Map, (!))
+import Data.Map qualified as Map
+import Data.Maybe (fromMaybe, isJust)
+import Data.Proxy
+import GHC.IORef (IORef (IORef))
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+client :: Proxy "client"
+client = Proxy
+
+primary :: Proxy "primary"
+primary = Proxy
+
+backup1 :: Proxy "backup1"
+backup1 = Proxy
+
+backup2 :: Proxy "backup2"
+backup2 = Proxy
+
+type State = Map String String
+
+data Request = Put String String | Get String deriving (Show, Read)
+
+type Response = Maybe String
+
+-- | `readRequest` reads a request from the terminal.
+readRequest :: IO Request
+readRequest = do
+  putStrLn "Command?"
+  line <- getLine
+  case parseRequest line of
+    Just t -> return t
+    Nothing -> putStrLn "Invalid command" >> readRequest
+  where
+    parseRequest :: String -> Maybe Request
+    parseRequest s =
+      let l = words s
+       in case l of
+            ["GET", k] -> Just (Get k)
+            ["PUT", k, v] -> Just (Put k v)
+            _ -> Nothing
+
+-- | `handleRequest` handle a request and returns the new the state.
+handleRequest :: Request -> IORef State -> IO Response
+handleRequest request stateRef = case request of
+  Put key value -> do
+    modifyIORef stateRef (Map.insert key value)
+    return (Just value)
+  Get key -> do
+    state <- readIORef stateRef
+    return (Map.lookup key state)
+
+-- | ReplicationStrategy specifies how a request should be handled on possibly replicated servers
+-- `a` is a type that represent states across locations
+type ReplicationStrategy a = Request @ "primary" -> a -> Choreo IO (Response @ "primary")
+
+-- | `nullReplicationStrategy` is a replication strategy that does not replicate the state.
+nullReplicationStrategy :: ReplicationStrategy (IORef State @ "primary")
+nullReplicationStrategy request stateRef = do
+  primary `locally` \unwrap -> case unwrap request of
+    Put key value -> do
+      modifyIORef (unwrap stateRef) (Map.insert key value)
+      return (Just value)
+    Get key -> do
+      state <- readIORef (unwrap stateRef)
+      return (Map.lookup key state)
+
+-- | `doBackup` relays a mutating request to a backup location.
+doBackup ::
+  KnownSymbol a =>
+  KnownSymbol b =>
+  Proxy a ->
+  Proxy b ->
+  Request @ a ->
+  IORef State @ b ->
+  Choreo IO ()
+doBackup locA locB request stateRef = do
+  cond (locA, request) \case
+    Put _ _ -> do
+      request' <- (locA, request) ~> locB
+      (locB, \unwrap -> handleRequest (unwrap request') (unwrap stateRef))
+        ~~> locA
+      return ()
+    _ -> do
+      return ()
+
+-- | `primaryBackupReplicationStrategy` is a replication strategy that replicates the state to a backup server.
+primaryBackupReplicationStrategy :: ReplicationStrategy (IORef State @ "primary", IORef State @ "backup1")
+primaryBackupReplicationStrategy request (primaryStateRef, backupStateRef) = do
+  -- relay request to backup if it is mutating (= PUT)
+  doBackup primary backup1 request backupStateRef
+
+  -- process request on primary
+  primary `locally` \unwrap -> handleRequest (unwrap request) (unwrap primaryStateRef)
+
+-- | `doubleBackupReplicationStrategy` is a replication strategy that replicates the state to two backup servers.
+doubleBackupReplicationStrategy ::
+  ReplicationStrategy
+    (IORef State @ "primary", IORef State @ "backup1", IORef State @ "backup2")
+doubleBackupReplicationStrategy
+  request
+  (primaryStateRef, backup1StateRef, backup2StateRef) = do
+    -- relay to two backup locations
+    doBackup primary backup1 request backup1StateRef
+    doBackup primary backup2 request backup2StateRef
+
+    -- process request on primary
+    primary `locally` \unwrap ->
+      handleRequest (unwrap request) (unwrap primaryStateRef)
+
+-- | `kvs` is a choreography that processes a single request at the client and returns the response.
+-- It uses the provided replication strategy to handle the request.
+kvs :: Request @ "client" -> a -> ReplicationStrategy a -> Choreo IO (Response @ "client")
+kvs request stateRefs replicationStrategy = do
+  request' <- (client, request) ~> primary
+
+  -- call the provided replication strategy
+  response <- replicationStrategy request' stateRefs
+
+  -- send response to client
+  (primary, response) ~> client
+
+-- | `nullReplicationChoreo` is a choreography that uses `nullReplicationStrategy`.
+nullReplicationChoreo :: Choreo IO ()
+nullReplicationChoreo = do
+  stateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  loop stateRef
+  where
+    loop :: IORef State @ "primary" -> Choreo IO ()
+    loop stateRef = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRef nullReplicationStrategy
+      client `locally` \unwrap -> do putStrLn (show (unwrap response))
+      loop stateRef
+
+-- | `primaryBackupChoreo` is a choreography that uses `primaryBackupReplicationStrategy`.
+primaryBackupChoreo :: Choreo IO ()
+primaryBackupChoreo = do
+  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  backupStateRef <- backup1 `locally` \_ -> newIORef (Map.empty :: State)
+  loop (primaryStateRef, backupStateRef)
+  where
+    loop :: (IORef State @ "primary", IORef State @ "backup1") -> Choreo IO ()
+    loop stateRefs = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRefs primaryBackupReplicationStrategy
+      client `locally` \unwrap -> do putStrLn (show (unwrap response))
+      loop stateRefs
+
+-- | `doubleBackupChoreo` is a choreography that uses `doubleBackupReplicationStrategy`.
+doubleBackupChoreo :: Choreo IO ()
+doubleBackupChoreo = do
+  primaryStateRef <- primary `locally` \_ -> newIORef (Map.empty :: State)
+  backup1StateRef <- backup1 `locally` \_ -> newIORef (Map.empty :: State)
+  backup2StateRef <- backup2 `locally` \_ -> newIORef (Map.empty :: State)
+  loop (primaryStateRef, backup1StateRef, backup2StateRef)
+  where
+    loop :: (IORef State @ "primary", IORef State @ "backup1", IORef State @ "backup2") -> Choreo IO ()
+    loop stateRefs = do
+      request <- client `locally` \_ -> readRequest
+      response <- kvs request stateRefs doubleBackupReplicationStrategy
+      client `locally` \unwrap -> do putStrLn ("> " ++ show (unwrap response))
+      loop stateRefs
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "client" -> runChoreography config primaryBackupChoreo "client"
+    "primary" -> runChoreography config primaryBackupChoreo "primary"
+    "backup1" -> runChoreography config primaryBackupChoreo "backup1"
+    "backup2" -> runChoreography config primaryBackupChoreo "backup2"
+  return ()
+  where
+    config =
+      mkHttpConfig
+        [ ("client", ("localhost", 3000)),
+          ("primary", ("localhost", 4000)),
+          ("backup1", ("localhost", 5000)),
+          ("backup2", ("localhost", 6000))
+        ]
diff --git a/examples/HasChor/mergesort/Main.hs b/examples/HasChor/mergesort/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/mergesort/Main.hs
@@ -0,0 +1,118 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Http
+import Data.Proxy
+import Data.Time
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+divide :: [a] -> ([a], [a])
+divide xs = splitAt lhx xs
+  where
+    lhx = length xs `div` 2
+
+primary :: Proxy "primary"
+primary = Proxy
+
+worker1 :: Proxy "worker1"
+worker1 = Proxy
+
+worker2 :: Proxy "worker2"
+worker2 = Proxy
+
+sort ::
+  KnownSymbol a =>
+  Proxy a ->
+  KnownSymbol b =>
+  Proxy b ->
+  KnownSymbol c =>
+  Proxy c ->
+  ([Int] @ a) ->
+  Choreo IO ([Int] @ a)
+sort a b c lst = do
+  condition <- a `locally` \unwrap -> do return $ length (unwrap lst) > 1
+  cond (a, condition) \case
+    True -> do
+      pivot <- a `locally` \unwrap -> do return $ length (unwrap lst) `div` 2
+      divided <- a `locally` \unwrap -> do return $ divide (unwrap lst)
+      l <- a `locally` \unwrap -> do return $ fst (unwrap divided)
+      r <- a `locally` \unwrap -> do return $ snd (unwrap divided)
+      l' <- (a, l) ~> b
+      r' <- (a, r) ~> c
+      ls' <- sort b c a l'
+      rs' <- sort c a b r'
+      merge a b c ls' rs'
+    False -> do
+      return lst
+
+merge ::
+  KnownSymbol a =>
+  Proxy a ->
+  KnownSymbol b =>
+  Proxy b ->
+  KnownSymbol c =>
+  Proxy c ->
+  [Int] @ b ->
+  [Int] @ c ->
+  Choreo IO ([Int] @ a)
+merge a b c lhs rhs = do
+  lhsHasElements <- b `locally` \unwrap -> do return $ not (null (unwrap lhs))
+  cond (b, lhsHasElements) \case
+    True -> do
+      rhsHasElements <- c `locally` \unwrap -> do return $ not (null (unwrap rhs))
+      cond (c, rhsHasElements) \case
+        True -> do
+          rhsHeadAtC <- c `locally` \unwrap -> do return $ head (unwrap rhs)
+          rhsHeadAtB <- (c, rhsHeadAtC) ~> b
+          takeLhs <- b `locally` \unwrap -> do return $ head (unwrap lhs) <= unwrap rhsHeadAtB
+          cond (b, takeLhs) \case
+            True -> do
+              -- take (head lhs) and merge the rest
+              lhs' <- b `locally` \unwrap -> do return $ tail (unwrap lhs)
+              merged <- merge a b c lhs' rhs
+              lhsHeadAtB <- b `locally` \unwrap -> do return $ head (unwrap lhs)
+              lhsHeadAtA <- (b, lhsHeadAtB) ~> a
+              a `locally` \unwrap -> do return $ unwrap lhsHeadAtA : unwrap merged
+            False -> do
+              -- take (head rhs) and merge the rest
+              rhs' <- c `locally` \unwrap -> do return $ tail (unwrap rhs)
+              merged <- merge a b c lhs rhs'
+              rhsHeadAtC <- c `locally` \unwrap -> do return $ head (unwrap rhs)
+              rhsHeadAtA <- (c, rhsHeadAtC) ~> a
+              a `locally` \unwrap -> do return $ unwrap rhsHeadAtA : unwrap merged
+        False -> do
+          (b, lhs) ~> a
+    False -> do
+      (c, rhs) ~> a
+
+mainChoreo :: Choreo IO ()
+mainChoreo = do
+  lst <- primary `locally` \unwrap -> do return [1, 6, 5, 3, 4, 2, 7, 8]
+  sorted <- sort primary worker1 worker2 lst
+  primary `locally` \unwrap -> do
+    print (unwrap sorted)
+    return ()
+  return ()
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  case loc of
+    "primary" -> runChoreography config mainChoreo "primary"
+    "worker1" -> runChoreography config mainChoreo "worker1"
+    "worker2" -> runChoreography config mainChoreo "worker2"
+  return ()
+  where
+    config =
+      mkHttpConfig
+        [ ("primary", ("localhost", 3000)),
+          ("worker1", ("localhost", 4000)),
+          ("worker2", ("localhost", 5000))
+        ]
diff --git a/examples/HasChor/playground/Main.hs b/examples/HasChor/playground/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/playground/Main.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds      #-}
+{-# LANGUAGE LambdaCase     #-}
+
+module Main where
+
+import Choreography
+import Control.Monad
+import Data.Proxy
+import System.Environment
+
+-- Step 1: Defining locations
+alice :: Proxy "alice"
+alice = Proxy
+
+-- Step 2: Writing a choreography
+choreography :: Choreo IO (() @ "alice")
+choreography = do
+  alice `locally` \_ -> putStrLn "Hello, world!"
+
+-- Step 3: Projecting and running the chreography
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    [loc] -> void $ runChoreography cfg choreography loc
+    _     -> error "wrong usage: must provide exactly one location"
+  where
+    -- Step 4: Mapping locations to HTTP ports
+    cfg = mkHttpConfig [ ("alice", ("localhost", 4242))
+                       ]
diff --git a/examples/HasChor/quicksort/Main.hs b/examples/HasChor/quicksort/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/quicksort/Main.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE LambdaCase #-}
+
+module Main where
+
+import Choreography (runChoreography)
+import Choreography.Choreo
+import Choreography.Location
+import Choreography.Network.Local
+import Control.Concurrent.Async (async, mapConcurrently_, wait)
+import Data.Proxy
+import Data.Time
+import GHC.TypeLits (KnownSymbol)
+import System.Environment
+
+primary :: Proxy "primary"
+primary = Proxy
+
+worker1 :: Proxy "worker1"
+worker1 = Proxy
+
+worker2 :: Proxy "worker2"
+worker2 = Proxy
+
+quicksort :: (KnownSymbol a, KnownSymbol b, KnownSymbol c) => Proxy a -> Proxy b -> Proxy c -> [Int] @ a -> Choreo IO ([Int] @ a)
+quicksort a b c lst = do
+  isEmpty <- a `locally` \unwrap -> pure (null (unwrap lst))
+  cond (a, isEmpty) \case
+    True -> do
+      a `locally` \_ -> pure []
+    False -> do
+      smaller <- (a, \unwrap -> let x : xs = unwrap lst in pure [i | i <- xs, i <= x]) ~~> b
+      smaller' <- quicksort b c a smaller
+      smaller'' <- (b, smaller') ~> a
+      bigger <- (a, \unwrap -> let x : xs = unwrap lst in pure [i | i <- xs, i > x]) ~~> c
+      bigger' <- quicksort c a b bigger
+      bigger'' <- (c, bigger') ~> a
+      a `locally` \unwrap -> pure $ unwrap smaller'' ++ [head (unwrap lst)] ++ unwrap bigger''
+
+mainChoreo :: Choreo IO ()
+mainChoreo = do
+  lst <- primary `locally` \unwrap -> do return [1, 6, 5, 3, 4, 2, 7, 8]
+  sorted <- quicksort primary worker1 worker2 lst
+  primary `locally` \unwrap -> do
+    print (unwrap sorted)
+    return ()
+  return ()
+
+main :: IO ()
+main = do
+  config <- mkLocalConfig locs
+  mapConcurrently_ (runChoreography config mainChoreo) locs
+  return ()
+  where
+    locs = ["primary", "worker1", "worker2"]
diff --git a/examples/HasChor/ring-leader/Main.hs b/examples/HasChor/ring-leader/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/HasChor/ring-leader/Main.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds      #-}
+{-# LANGUAGE LambdaCase     #-}
+
+module Main where
+
+import Choreography
+import Data.Proxy
+import GHC.TypeLits (KnownSymbol)
+import Control.Monad
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.State
+import System.Environment
+
+-- an edge of the ring is represented as a tuple of two locaitons l and l' where
+-- l is on the left of l'
+data Edge = forall l l'.
+  (KnownSymbol l, KnownSymbol l') => Edge (Proxy l) (Proxy l')
+
+-- a ring is a sequence of edges
+type Ring = [Edge]
+
+type Label = Int
+
+ringLeader :: Ring -> Choreo (StateT Label IO) ()
+ringLeader ring = loop ring
+  where
+    loop :: Ring -> Choreo (StateT Label IO) ()
+    loop []     = loop ring
+    loop (x:xs) = do
+      finished <- talkToRight x
+      if finished
+      then return ()
+      else loop xs
+
+    talkToRight :: Edge -> Choreo (StateT Label IO) Bool
+    talkToRight (Edge left right) = do
+      labelLeft  <- (left, \_ -> get) ~~> right
+      labelRight <- right `locally` \_ -> get
+
+      finished <- right `locally` \un ->
+        return $ un labelLeft == un labelRight
+
+      cond (right, finished) \case
+        True  -> do
+          right `locally` \_ -> lift $ putStrLn "I'm the leader"
+          return True
+        False -> do
+          right `locally` \un -> put (max (un labelLeft) (un labelRight))
+          return False
+
+nodeA :: Proxy "A"
+nodeA = Proxy
+
+nodeB :: Proxy "B"
+nodeB = Proxy
+
+nodeC :: Proxy "C"
+nodeC = Proxy
+
+nodeD :: Proxy "D"
+nodeD = Proxy
+
+ring = [ Edge nodeA nodeB
+       , Edge nodeB nodeC
+       , Edge nodeC nodeD
+       , Edge nodeD nodeA
+       ]
+
+main :: IO ()
+main = do
+  [loc] <- getArgs
+  putStrLn "Please input a label:"
+  label <- read <$> getLine
+  runStateT (runChoreography config (ringLeader ring) loc) label
+  return ()
+  where
+    config = mkHttpConfig [ ("A", ("localhost", 4242))
+                          , ("B", ("localhost", 4343))
+                          , ("C", ("localhost", 4444))
+                          , ("D", ("localhost", 4545))
+                          ]
diff --git a/examples/clean-room/Main.hs b/examples/clean-room/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/clean-room/Main.hs
@@ -0,0 +1,170 @@
+{-# OPTIONS_GHC -Wno-redundant-constraints #-}
+
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs              #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE LambdaCase         #-}
+{-# LANGUAGE ParallelListComp   #-}
+
+module Main where
+
+import Control.Monad(replicateM)
+import Data.Foldable(fold)
+import Data.Function((&))
+import Data.Proxy(Proxy(..))
+import Data.SOP.BasicFunctors(mapKK, K (..))
+import Data.SOP.Classes(HCollapse(..), hmap, hsequenceK)
+import Data.SOP.Constraint(All)
+import Data.SOP.NP(NP(..))
+import GHC.TypeLits(KnownSymbol, SSymbol, withKnownSymbol, withSomeSSymbol)
+import System.Environment(getArgs)
+import System.Random(randomIO, randomRIO)
+
+import Choreography
+import Choreography.Location
+import Choreography.Location.Multi
+
+-- Schema and dataset handling (illustrative)
+
+-- For simplicity, all datasets follow the same trivial schema: a list of numbers
+data Schema = Schema
+  deriving stock (Show, Read)
+
+newtype Dataset = Dataset [Int]
+  deriving stock (Show, Read)
+  deriving newtype (Semigroup, Monoid)
+
+data Query
+  = Len
+  | Sum
+  | Avg
+  | Min
+  | Max
+  deriving stock (Eq, Ord, Show, Read, Enum, Bounded)
+
+loadSchema :: KnownSymbol l => Proxy l -> IO Schema
+loadSchema _ = pure Schema
+
+mergeSchemas :: [Schema] -> Schema
+mergeSchemas _ = Schema
+
+conformsToSchema :: Schema -> Dataset -> IO Bool
+conformsToSchema _ _ = randomIO -- To simulate failures
+
+emptyDataset :: Dataset
+emptyDataset = Dataset []
+
+-- Simulate some I/O to load a dataset (e.g. from a file)
+loadDataset :: KnownSymbol l => Proxy l -> IO Dataset
+loadDataset _ = do
+  n <- randomRIO (5, 10)
+  Dataset <$> replicateM n (randomRIO (-100, 100))
+
+mergeDatasets :: [Dataset] -> Dataset
+mergeDatasets = fold
+
+-- Simulate the selection of a query (by picking one randomly)
+loadQuery :: KnownSymbol l => Proxy l -> Schema -> IO Query
+loadQuery _ _ = toEnum <$> randomRIO (fromEnum @Query minBound, fromEnum @Query maxBound)
+
+agreeWithQueries :: KnownSymbol l => Proxy l -> [Query] -> IO Bool
+agreeWithQueries _ _ = pure True
+
+runQuery :: Dataset -> Query -> IO Int
+runQuery (Dataset ds) q =
+  pure $
+    case q of
+      Len -> length ds
+      Sum -> sum ds
+      Avg -> sum ds `quot` length ds
+      Min -> minimum ds
+      Max -> maximum ds
+
+-- Error-handling (if datasets don't conform to schema)
+
+getClientDataset :: (KnownSymbol server, KnownSymbol client) => Int -> Schema @ server -> Proxy server -> Proxy client -> Choreo IO (Dataset @ server)
+getClientDataset tries schema server client =
+  if tries <= 0 then
+    pure $ wrap emptyDataset
+  else do
+    dataset <- (client, \_ -> loadDataset client) ~~> server
+    cond_' (server, \unwrap -> conformsToSchema (unwrap schema) (unwrap dataset)) \case
+      True -> pure dataset
+      False -> getClientDataset (tries - 1) schema server client
+
+getClientDatasets :: (KnownSymbol server, All KnownSymbol clients) => Int -> Schema @ server -> Proxy server -> NP Proxy clients -> Choreo IO (NP (K Dataset) clients @ server)
+getClientDatasets _ _ _ Nil = pure $ wrap Nil
+getClientDatasets tries schema server (client :* clients) = do
+  dataset <- getClientDataset tries schema server client
+  datasets <- getClientDatasets tries schema server clients
+  locally server \unwrap -> pure $ K (unwrap dataset) :* unwrap datasets
+
+-- Main choreography
+
+cleanRoom :: (KnownSymbol server, All KnownSymbol clients) => Proxy server -> NP Proxy clients -> Choreo IO ()
+cleanRoom server clients = do
+  -- Every client loads their data schema and sends it to the server
+  schemas <- (clients, \c _ -> loadSchema c) *~~> server
+
+  -- Server merges the schemas
+  finalSchemaS <- locally server \unwrap -> pure $ mergeSchemas $ hcollapse $ unwrap schemas
+
+  -- Server broadcasts the final schema
+  finalSchema <- (server, finalSchemaS) ~>* clients
+
+  -- Based on the final schema, each client computes what query it wants to run, and sends it back to the server
+  queriesS <- (clients, \c unwrap -> loadQuery c $ unwrap finalSchema) *~~> server
+
+  -- Server broadcasts the list of queries to all clients
+  queries <- (server, \unwrap -> pure $ hcollapse $ unwrap queriesS) ~~>* clients
+
+  -- Each client locally decides if they agree with everyone else's queries, and sends back a response
+  agreements <- (clients, \c unwrap -> agreeWithQueries c $ unwrap queries) *~~> server
+
+  -- Server branches on the responses
+  cond_' (server, \unwrap -> pure $ and $ hcollapse $ unwrap agreements) \case
+    False -> do -- Someone disagrees, end the protocol
+      multilocally clients \_ _ -> print "Disagreement"
+      pure $ wrap ()
+    True -> do -- Everyone agrees, proceed
+      -- The amount of times the server will ask for a valid dataset
+      let tries = 5
+
+      -- Clients send their datasets to the server
+      datasets <- getClientDatasets tries finalSchemaS server clients
+
+      -- Server locally merges the datasets and runs all the queries
+      resultsS <- locally server \unwrap -> do
+        let aggregatedDataset = mergeDatasets $ hcollapse $ unwrap datasets
+        hsequenceK $ hmap (mapKK $ runQuery aggregatedDataset) $ unwrap queriesS
+
+      -- Server sends the result of each query "pointwise"
+      results <- (server, resultsS) ~>. clients
+
+      -- Clients consume the received query results
+      multilocally clients \_ unwrap -> print $ unwrap results
+      pure $ wrap ()
+
+  pure ()
+
+-- Running the choreography
+
+withProxy :: String -> (forall s. KnownSymbol s => Proxy s -> a) -> a
+withProxy s f = withSomeSSymbol s \(ss :: SSymbol s) -> withKnownSymbol ss $ f @s Proxy
+
+withProxies :: [String] -> (forall ss. All KnownSymbol ss => NP Proxy ss -> a) -> a
+withProxies [] f = f Nil
+withProxies (s : ss) f =
+  withProxy s \s ->
+    withProxies ss \ss ->
+      f $ s :* ss
+
+httpConfig :: [String] -> HttpConfig
+httpConfig ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000..]]
+
+main :: IO ()
+main = do
+  self : ls@(server : clients) <- getArgs
+  runChoreography (httpConfig ls) (cleanRoom & withProxy server & withProxies clients) self
diff --git a/examples/unsoundness/Main.hs b/examples/unsoundness/Main.hs
new file mode 100644
--- /dev/null
+++ b/examples/unsoundness/Main.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE DataKinds          #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GADTs              #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE ParallelListComp   #-}
+{-# LANGUAGE LambdaCase         #-}
+{-# LANGUAGE ViewPatterns       #-}
+
+module Main where
+
+import Data.Proxy(Proxy(..))
+import Data.IORef (newIORef, readIORef, writeIORef)
+import Control.Concurrent (forkIO, killThread, threadDelay)
+import Control.Monad.IO.Class (MonadIO(..))
+import System.Environment(getArgs)
+
+import Servant.API
+import Servant.Server (serve)
+import Network.Wai.Handler.Warp (run)
+import Network.HTTP.Client (defaultManagerSettings, newManager)
+import Servant.Client (client, runClientM, BaseUrl(..), mkClientEnv, Scheme(..))
+
+import Choreography
+import Choreography.Location
+import System.Exit (exitSuccess)
+
+a :: Proxy "a"
+a = Proxy
+
+b :: Proxy "b"
+b = Proxy
+
+c :: Proxy "c"
+c = Proxy
+
+type API = "send" :> Capture "from" LocTm :> ReqBody '[PlainText] String :> PostNoContent
+
+httpSend :: Show a => LocTm -> Int -> a -> IO ()
+httpSend sender targetPort a = liftIO $ do
+    mgr <- newManager defaultManagerSettings
+    res <- runClientM (client @API Proxy sender $ show a) $ mkClientEnv mgr $ BaseUrl Http "localhost" targetPort ""
+
+    case res of
+      Left err -> putStrLn $ "Error: " <> show err
+      Right _  -> pure ()
+
+    threadDelay 1000000
+
+httpRecv :: Int -> IO (Maybe String)
+httpRecv port = do
+  ref <- newIORef Nothing
+
+  recvThread <- forkIO do
+    run port $ serve @API Proxy \_ msg -> do
+      NoContent <$ liftIO (writeIORef ref $ Just msg)
+
+  threadDelay 1000000
+  killThread recvThread
+
+  readIORef ref
+
+-- "Frontrun" choreographic communication by sending data to the same port.
+frontrun :: Choreo IO ()
+frontrun = do
+  locally a \_ -> do
+    httpSend "a" 3001 False
+    exitSuccess
+
+  cond (a, wrap True) \case
+    False -> pure ()
+    True -> do
+      (b, wrap 2) ~> c
+      pure ()
+
+-- Open an an "under-the-table" HTTP connecion between `a` and `b` to exfiltrate data from `c`.
+collusion :: Choreo IO ()
+collusion = do
+  locally a \_ -> do
+    v <- httpRecv 2999
+    print v
+
+  vb <- (c, wrap 2) ~> b
+
+  locally b \unwrap -> do
+    httpSend "b" 2999 $ unwrap vb
+
+  pure ()
+
+-- Helpers for running the choreographies
+
+choreos :: [Choreo IO ()]
+choreos = [frontrun, collusion]
+
+-- Assigns different ports to each choreography so they can be run in parallel.
+httpConfig :: Int -> [LocTm] -> HttpConfig
+httpConfig choreo ls = mkHttpConfig [(l, ("localhost", port)) | l <- ls | port <- [3000 + 1000 * choreo..]]
+
+main :: IO ()
+main = do
+  [read -> choreo, self] <- getArgs
+  runChoreography (httpConfig choreo ["a", "b", "c"]) (choreos !! choreo) self
diff --git a/src/Choreography.hs b/src/Choreography.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE ExplicitNamespaces #-}
+
+-- | This module defines the interface to HasChor. The client of the library is
+-- highly recommended to only use constructs exported by this module.
+module Choreography (
+  -- * Locations and Located Values
+  LocTm,
+  LocTy,
+  type (@),
+  mkLoc,
+
+  -- * The Choreo monad
+  Choreo,
+  -- ** Choreo operations
+  locally,
+  (~>),
+  (~~>),
+  cond,
+  cond',
+  cond_,
+  cond_',
+
+  -- * Message transport backends
+  -- ** The HTTP backend
+  Host,
+  Port,
+  HttpConfig,
+  mkHttpConfig,
+
+  -- * Running choreographies
+  runChoreo,
+  runChoreography
+  ) where
+
+import Choreography.Location
+import Choreography.Choreo
+import Choreography.Network
+import Choreography.Network.Http
+import Control.Monad.IO.Class
+
+-- | Run a choreography with a message transport backend.
+runChoreography :: (Backend config, MonadIO m) => config -> Choreo m a -> LocTm -> m a
+runChoreography cfg choreo l = runNetwork cfg l (epp (locs cfg) choreo l)
diff --git a/src/Choreography/Choreo.hs b/src/Choreography/Choreo.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Choreo.hs
@@ -0,0 +1,190 @@
+{-# LANGUAGE BlockArguments     #-}
+{-# LANGUAGE GADTs              #-}
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE LambdaCase         #-}
+
+-- | This module defines `Choreo`, the monad for writing choreographies.
+module Choreography.Choreo where
+
+import Control.Applicative(empty)
+import Control.Monad.State.Strict(StateT, execStateT, modify')
+import Data.Foldable(foldMap')
+import Data.Functor(($>))
+import Data.List(delete)
+import Data.Maybe(fromMaybe)
+import Data.Proxy(Proxy(..))
+import Data.Set(Set)
+import Data.Set qualified as S
+import GHC.TypeLits(KnownSymbol)
+
+import Choreography.Location
+import Choreography.Network
+import Control.Monad.Freer
+
+-- * The Choreo monad
+
+-- | A constrained version of `unwrap` that only unwraps values located at a
+-- specific location.
+type Unwrap l = forall a. a @ l -> a
+
+-- | Effect signature for the `Choreo` monad. @m@ is a monad that represents
+-- local computations.
+data ChoreoSig m a where
+  Local :: (KnownSymbol l)
+        => Proxy l
+        -> (Unwrap l -> m a)
+        -> ChoreoSig m (a @ l)
+
+  Comm :: (Show a, Read a, KnownSymbol l, KnownSymbol l')
+       => Proxy l
+       -> a @ l
+       -> Proxy l'
+       -> ChoreoSig m (a @ l')
+
+  Cond :: (Show a, Read a, KnownSymbol l)
+       => Proxy l
+       -> a @ l
+       -> (a -> Choreo m b)
+       -> ChoreoSig m b
+
+  Cond_ :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)
+        => Proxy l
+        -> a @ l
+        -> (a -> Choreo m (b @ l))
+        -> ChoreoSig m (b @ l)
+
+-- | Monad for writing choreographies.
+type Choreo m = Freer (ChoreoSig m)
+
+-- | Run a `Choreo` monad directly.
+runChoreo :: Monad m => Choreo m a -> m a
+runChoreo = interpFreer handler
+  where
+    handler :: Monad m => ChoreoSig m a -> m a
+    handler (Local _ m)   = wrap <$> m unwrap
+    handler (Comm _ a _)  = return $ (wrap . unwrap) a
+    handler (Cond _ a c)  = runChoreo $ c (unwrap a)
+    handler (Cond_ _ a c) = runChoreo $ c (unwrap a)
+
+-- Static analysis
+
+insertLoc :: KnownSymbol l => Proxy l -> Set LocTm -> Set LocTm
+insertLoc = S.insert . toLocTm
+
+insertLocs :: [LocTm] -> Set LocTm -> Set LocTm
+insertLocs = S.union . S.fromList
+
+-- | Apply function `f` to every possible value of type `a`, and collect all the resulting `participants`.
+enumerate :: (Bounded a, Enum a) => [LocTm] -> (a -> Choreo m b) -> Set LocTm
+enumerate ls f = foldMap' (participants ls . f) [minBound ..]
+
+-- | Compute the set of locations participating in this choreography.
+participants :: [LocTm] -> Choreo m a -> Set LocTm
+participants ls = fromMaybe (S.fromList ls) . (`execStateT` S.empty) . interpFreer handler
+  where
+    handler :: ChoreoSig m a -> StateT (Set LocTm) Maybe a
+    handler = \case
+      Local l _   -> modify' (insertLoc l)                $> Empty
+      Comm l _ l' -> modify' (insertLoc l . insertLoc l') $> Empty
+      Cond _ _ _  -> modify' (insertLocs ls)              *> empty
+      Cond_ l _ f -> modify' (insertLoc l . S.union (enumerate ls f)) $> Empty
+
+-- | Endpoint projection.
+epp :: [LocTm] -> Choreo m a -> LocTm -> Network m a
+epp ls c l' = interpFreer handler c
+  where
+    handler :: ChoreoSig m a -> Network m a
+    handler (Local l m)
+      | toLocTm l == l' = wrap <$> run (m unwrap)
+      | otherwise       = return Empty
+    handler (Comm s a r)
+      | l' == s' && s' == r' = return $ wrap (unwrap a)
+      | s' == l'             = send (unwrap a) r' >> return Empty
+      | r' == l'             = wrap <$> recv s'
+      | otherwise            = return Empty
+      where
+        s' = toLocTm s
+        r' = toLocTm r
+    handler (Cond l a c)
+      | toLocTm l == l' = broadcast (unwrap a) (delete l' ls) >> epp ls (c (unwrap a)) l'
+      | otherwise       = recv (toLocTm l) >>= \x -> epp ls (c x) l'
+    handler (Cond_ pl a c)
+      | l == l'         = broadcast (unwrap a) (S.toList $ S.delete l ls') >> epp ls (c (unwrap a)) l'
+      | S.member l' ls' = recv l >>= \x -> epp ls (c x) l'
+      | otherwise       = pure Empty
+      where
+        l = toLocTm pl
+        ls' = enumerate ls c
+
+-- * Choreo operations
+
+-- | Perform a local computation at a given location.
+locally :: KnownSymbol l
+        => Proxy l           -- ^ Location performing the local computation.
+        -> (Unwrap l -> m a) -- ^ The local computation given a constrained
+                             -- unwrap funciton.
+        -> Choreo m (a @ l)
+locally l m = toFreer (Local l m)
+
+-- | Communication between a sender and a receiver.
+(~>) :: (Show a, Read a, KnownSymbol l, KnownSymbol l')
+     => (Proxy l, a @ l)  -- ^ A pair of a sender's location and a value located
+                          -- at the sender
+     -> Proxy l'          -- ^ A receiver's location.
+     -> Choreo m (a @ l')
+(~>) (l, a) l' = toFreer (Comm l a l')
+
+-- | Conditionally execute choreographies based on a located value.
+cond :: (Show a, Read a, KnownSymbol l)
+     => (Proxy l, a @ l)  -- ^ A pair of a location and a scrutinee located on
+                          -- it.
+     -> (a -> Choreo m b) -- ^ A function that describes the follow-up
+                          -- choreographies based on the value of scrutinee.
+     -> Choreo m b
+cond (l, a) c = toFreer (Cond l a c)
+
+-- | Conditionally execute choreographies at participating locations,
+-- based on a located value.
+cond_ :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)
+      => (Proxy l, a @ l)        -- ^ A pair of a location and a scrutinee located on
+                                 -- it.
+      -> (a -> Choreo m (b @ l)) -- ^ A function that describes the follow-up
+                                 -- choreographies based on the value of scrutinee.
+      -> Choreo m (b @ l)
+cond_ (l, a) c = toFreer (Cond_ l a c)
+
+-- | A variant of `~>` that sends the result of a local computation.
+(~~>) :: (Show a, Read a, KnownSymbol l, KnownSymbol l')
+      => (Proxy l, Unwrap l -> m a) -- ^ A pair of a sender's location and a local
+                                    -- computation.
+      -> Proxy l'                   -- ^ A receiver's location.
+      -> Choreo m (a @ l')
+(~~>) (l, m) l' = do
+  x <- l `locally` m
+  (l, x) ~> l'
+
+-- | A variant of `cond` that conditonally executes choregraphies based on the
+-- result of a local computation.
+cond' :: (Show a, Read a, KnownSymbol l)
+      => (Proxy l, Unwrap l -> m a) -- ^ A pair of a location and a local
+                                    -- computation.
+      -> (a -> Choreo m b)          -- ^ A function that describes the follow-up
+                                    -- choreographies based on the result of the
+                                    -- local computation.
+      -> Choreo m b
+cond' (l, m) c = do
+  x <- l `locally` m
+  cond (l, x) c
+
+-- | A variant of `cond_` that conditonally executes choregraphies at participating locations,
+-- based on the result of a local computation.
+cond_' :: (Bounded a, Enum a, Show a, Read a, KnownSymbol l)
+       => (Proxy l, Unwrap l -> m a) -- ^ A pair of a location and a local
+                                     -- computation.
+       -> (a -> Choreo m (b @ l))    -- ^ A function that describes the follow-up
+                                     -- choreographies based on the result of the
+                                     -- local computation.
+       -> Choreo m (b @ l)
+cond_' (l, m) c = do
+  x <- l `locally` m
+  cond_ (l, x) c
diff --git a/src/Choreography/Location.hs b/src/Choreography/Location.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Location.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE DataKinds #-}
+
+-- | This module defines locations and located values.
+module Choreography.Location where
+
+import Data.Proxy
+import GHC.TypeLits
+import Language.Haskell.TH
+
+-- | Term-level locations.
+type LocTm = String
+
+-- | Type-level locations.
+type LocTy = Symbol
+
+-- | Convert a type-level location to a term-level location.
+toLocTm :: forall (l :: LocTy). KnownSymbol l => Proxy l -> LocTm
+toLocTm = symbolVal
+
+-- | Located values.
+--
+-- @a \@ l@ represents a value of type @a@ at location @l@.
+data a @ (l :: LocTy)
+  = Wrap a -- ^ A located value @a \@ l@ from location @l@'s perspective.
+  | Empty  -- ^ A located value @a \@ l@ from locations other than @l@'s
+           -- perspective.
+
+-- | Wrap a value as a located value.
+wrap :: a -> a @ l
+wrap = Wrap
+
+-- | Unwrap a located value.
+--
+-- /Note:/ Unwrapping a empty located value will throw an exception.
+unwrap :: a @ l-> a
+unwrap (Wrap a) = a
+unwrap Empty    = error "this should never happen for a well-typed choreography"
+
+-- | Define a location at both type and term levels.
+mkLoc :: String -> Q [Dec]
+mkLoc loc = do
+  let locName = mkName loc
+  let p = mkName "Data.Proxy.Proxy"
+  pure [SigD locName (AppT (ConT p) (LitT (StrTyLit loc))),ValD (VarP locName) (NormalB (ConE p)) []]
diff --git a/src/Choreography/Location/Multi.hs b/src/Choreography/Location/Multi.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Location/Multi.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE BlockArguments        #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE ImpredicativeTypes    #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE TypeFamilies          #-}
+
+-- | This module defines multiply-located values and combinators for manipulating them.
+module Choreography.Location.Multi where
+
+import GHC.TypeLits(KnownSymbol)
+import Data.Proxy(Proxy(..))
+import Data.SOP.BasicFunctors(K(..), mapKK, unK)
+import Data.SOP.Classes(HSequence(..), hmap)
+import Data.SOP.Constraint(All)
+import Data.SOP.NP(NP(..), hd, tl)
+
+import Choreography.Choreo
+import Choreography.Location
+
+-- | The type of multiply-located values.
+type a @@ ls = NP ((@) a) ls
+
+-- | "scatter": Send a located value from a single location to many others.
+(~>*) :: (KnownSymbol l, All KnownSymbol ls, Show a, Read a) => (Proxy l, a @ l) -> NP Proxy ls -> Choreo m (a @@ ls)
+(l, a) ~>* ls = hctraverse' (Proxy @KnownSymbol) ((l, a) ~>) ls
+
+infix 4 ~>*
+
+-- | "gather": Send a multiply-located value from many locations to one.
+(*~>) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => a @@ ls -> Proxy l -> Choreo m (NP (K a) ls @ l)
+as *~> l = do
+  as' <- hctraverse' (Proxy @KnownSymbol) (\a -> K <$> ((Proxy, a) ~> l)) as
+  locally l \unwrap -> pure $ hmap (mapKK unwrap) as'
+
+infix 4 *~>
+
+-- | Send a list of values from one location to many others "pointwise" (i.e. each target gets one value).
+(~>.) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => (Proxy l, NP (K a) ls @ l) -> NP Proxy ls -> Choreo m (a @@ ls)
+_       ~>. Nil = pure Nil
+(l, as) ~>. (l' :* ls') = do
+  a <- locally l \unwrap -> pure $ unK $ hd $ unwrap as
+  as' <- locally l \unwrap -> pure $ tl $ unwrap as
+  (:*) <$> ((l, a) ~> l') <*> ((l, as') ~>. ls')
+
+infix 4 ~>.
+
+-- | Multiply-located version of `Unwrap`.
+type Unwraps ls = forall a. a @@ ls -> a
+
+-- | The type of a (multi)local computation.
+type LocalComp ls m a = forall l. KnownSymbol l => Proxy l -> Unwraps ls -> m a
+
+-- | Multiply-located version of `locally`.
+multilocally :: forall ls m a. All KnownSymbol ls => NP Proxy ls -> LocalComp ls m a -> Choreo m (a @@ ls)
+multilocally ls f = go ls id
+  where
+    go :: forall ls'. All KnownSymbol ls' => NP Proxy ls' -> (forall a. a @@ ls -> a @@ ls') -> Choreo m (a @@ ls')
+    go Nil _pick = pure Nil
+    go (l :* ls) pick = do
+      a <- locally l \unwrap -> f l $ unwrap . hd . pick
+      (a :*) <$> go ls (tl . pick)
+
+-- | A variant of `~>*` that sends the result of a local computation.
+(~~>*) :: (KnownSymbol l, All KnownSymbol ls, Show a, Read a) => (Proxy l, Unwrap l -> m a) -> NP Proxy ls -> Choreo m (a @@ ls)
+(l, f) ~~>* ls = do
+  a <- locally l f
+  (l, a) ~>* ls
+
+infix 4 ~~>*
+
+-- | A variant of `*~>` that sends the result of a local computation.
+(*~~>) :: (KnownSymbol l, All KnownSymbol ls, Applicative m, Show a, Read a) => (NP Proxy ls, LocalComp ls m a) -> Proxy l -> Choreo m (NP (K a) ls @ l)
+(ls, f) *~~> l = do
+  a <- multilocally ls f
+  a *~> l
+
+infix 4 *~~>
diff --git a/src/Choreography/Network.hs b/src/Choreography/Network.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Network.hs
@@ -0,0 +1,61 @@
+-- | This module defines the `Network` monad, which represents programs run on
+-- individual nodes in a distributed system with explicit sends and receives.
+-- To run a `Network` program, we provide a `runNetwork` function that supports
+-- multiple message transport backends.
+module Choreography.Network where
+
+import Choreography.Location
+import Control.Monad.Freer
+import Control.Monad.IO.Class
+
+-- * The Network monad
+
+-- | Effect signature for the `Network` monad.
+data NetworkSig m a where
+  -- | Local computation.
+  Run :: m a
+      -> NetworkSig m a
+  -- | Sending.
+  Send :: Show a
+       => a
+       -> LocTm
+       -> NetworkSig m ()
+  -- | Receiving.
+  Recv :: Read a
+       => LocTm
+       -> NetworkSig m a
+  -- | Broadcasting.
+  BCast :: Show a
+        => a
+        -> [LocTm]
+        -> NetworkSig m ()
+
+-- | Monad that represents network programs.
+type Network m = Freer (NetworkSig m)
+
+-- * Network operations
+
+-- | Perform a local computation.
+run :: m a -> Network m a
+run m = toFreer $ Run m
+
+-- | Send a message to a receiver.
+send :: Show a => a -> LocTm -> Network m ()
+send a l = toFreer $ Send a l
+
+-- | Receive a message from a sender.
+recv :: Read a => LocTm -> Network m a
+recv l = toFreer $ Recv l
+
+-- | Broadcast a message to all participants.
+broadcast :: Show a => a -> [LocTm] -> Network m ()
+broadcast a ls = toFreer $ BCast a ls
+
+-- * Message transport backends
+
+-- | A message transport backend defines a /configuration/ of type @c@ that
+-- carries necessary bookkeeping information, then defines @c@ as an instance
+-- of `Backend` and provides a `runNetwork` function.
+class Backend c where
+  locs :: c -> [LocTm]
+  runNetwork :: MonadIO m => c -> LocTm -> Network m a -> m a
diff --git a/src/Choreography/Network/Http.hs b/src/Choreography/Network/Http.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Network/Http.hs
@@ -0,0 +1,112 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE GADTs     #-}
+
+-- | This module implments the HTTP message transport backend for the `Network`
+-- monad.
+module Choreography.Network.Http where
+
+import Choreography.Location
+import Choreography.Network hiding (run)
+import Data.Proxy (Proxy(..))
+import Data.HashMap.Strict (HashMap, (!))
+import Data.HashMap.Strict qualified as HashMap
+import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
+import Servant.API hiding (Host)
+import Servant.Client (ClientM, client, runClientM, BaseUrl(..), mkClientEnv, Scheme(..))
+import Servant.Server (Handler, Server, serve)
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.Freer
+import Control.Monad.IO.Class
+import Network.Wai.Handler.Warp (run)
+
+-- * Servant API
+
+type API = "send" :> Capture "from" LocTm :> ReqBody '[PlainText] String :> PostNoContent
+
+-- * Http configuration
+
+-- | The HTTP backend configuration specifies how locations are mapped to
+-- network hosts and ports.
+newtype HttpConfig = HttpConfig
+  { locToUrl :: HashMap LocTm BaseUrl
+  }
+
+type Host = String
+type Port = Int
+
+-- | Create a HTTP backend configuration from a association list that maps
+-- locations to network hosts and ports.
+mkHttpConfig :: [(LocTm, (Host, Port))] -> HttpConfig
+mkHttpConfig = HttpConfig . HashMap.fromList . fmap (fmap f)
+  where
+    f :: (Host, Port) -> BaseUrl
+    f (host, port) = BaseUrl
+      { baseUrlScheme = Http
+      , baseUrlHost = host
+      , baseUrlPort = port
+      , baseUrlPath = ""
+      }
+
+locsHttp :: HttpConfig -> [LocTm]
+locsHttp = HashMap.keys . locToUrl
+
+-- * Receiving channels
+
+type RecvChans = HashMap LocTm (Chan String)
+
+mkRecvChans :: HttpConfig -> IO RecvChans
+mkRecvChans cfg = foldM f HashMap.empty (locsHttp cfg)
+  where
+    f :: HashMap LocTm (Chan String) -> LocTm
+      -> IO (HashMap LocTm (Chan String))
+    f hm l = do
+      c <- newChan
+      return $ HashMap.insert l c hm
+
+-- * HTTP backend
+
+runNetworkHttp :: MonadIO m => HttpConfig -> LocTm -> Network m a -> m a
+runNetworkHttp cfg self prog = do
+  mgr <- liftIO $ newManager defaultManagerSettings
+  chans <- liftIO $ mkRecvChans cfg
+  recvT <- liftIO $ forkIO (recvThread cfg chans)
+  result <- runNetworkMain mgr chans prog
+  -- liftIO $ threadDelay 1000000 -- wait until all outstanding requests to be completed
+  liftIO $ killThread recvT
+  return result
+  where
+    runNetworkMain :: MonadIO m => Manager -> RecvChans -> Network m a -> m a
+    runNetworkMain mgr chans = interpFreer handler
+      where
+        handler :: MonadIO m => NetworkSig m a -> m a
+        handler (Run m)      = m
+        handler (Send a l)   = liftIO $ do
+          res <- runClientM (send self $ show a) (mkClientEnv mgr (locToUrl cfg ! l))
+          case res of
+            Left err -> putStrLn $ "Error : " ++ show err
+            Right _  -> return ()
+        handler (Recv l)     = liftIO $ read <$> readChan (chans ! l)
+     -- handler (Recv l)     = liftIO $ putStrLn ("Recv from " <> l) *> (read <$> readChan (chans ! l))
+        handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls
+
+    api :: Proxy API
+    api = Proxy
+
+    send :: LocTm -> String -> ClientM NoContent
+    send = client api
+
+    server :: RecvChans -> Server API
+    server chans = handler
+      where
+        handler :: LocTm -> String -> Handler NoContent
+        handler rmt msg = do
+          liftIO $ writeChan (chans ! rmt) msg
+          return NoContent
+
+    recvThread :: HttpConfig -> RecvChans -> IO ()
+    recvThread cfg chans = run (baseUrlPort $ locToUrl cfg ! self ) (serve api $ server chans)
+
+instance Backend HttpConfig where
+  locs = locsHttp
+  runNetwork = runNetworkHttp
diff --git a/src/Choreography/Network/Local.hs b/src/Choreography/Network/Local.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Network/Local.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE GADTs #-}
+
+-- | This module defines the multi-thread backend for the `Network` monad.
+module Choreography.Network.Local where
+
+import Choreography.Location
+import Choreography.Network
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.Freer
+import Control.Monad.IO.Class
+import Data.HashMap.Strict (HashMap, (!))
+import Data.HashMap.Strict qualified as HashMap
+
+-- | Each location is associated with a message buffer which stores messages sent
+-- from other locations.
+type MsgBuf = HashMap LocTm (Chan String)
+
+newtype LocalConfig = LocalConfig
+  { locToBuf :: HashMap LocTm MsgBuf
+  }
+
+newEmptyMsgBuf :: [LocTm] -> IO MsgBuf
+newEmptyMsgBuf = foldM f HashMap.empty
+  where
+    f hash loc = do
+      chan <- newChan
+      return (HashMap.insert loc chan hash)
+
+mkLocalConfig :: [LocTm] -> IO LocalConfig
+mkLocalConfig locs = LocalConfig <$> foldM f HashMap.empty locs
+  where
+    f hash loc = do
+      buf <- newEmptyMsgBuf locs
+      return (HashMap.insert loc buf hash)
+
+locsLocal :: LocalConfig -> [LocTm]
+locsLocal = HashMap.keys . locToBuf
+
+runNetworkLocal :: MonadIO m => LocalConfig -> LocTm -> Network m a -> m a
+runNetworkLocal cfg self prog = interpFreer handler prog
+  where
+    handler :: MonadIO m => NetworkSig m a -> m a
+    handler (Run m)      = m
+    handler (Send a l)   = liftIO $ writeChan ((locToBuf cfg ! l) ! self) (show a)
+    handler (Recv l)     = liftIO $ read <$> readChan ((locToBuf cfg ! self) ! l)
+    handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls
+
+instance Backend LocalConfig where
+  locs = locsLocal
+  runNetwork = runNetworkLocal
diff --git a/src/Choreography/Network/LocalMsgCount.hs b/src/Choreography/Network/LocalMsgCount.hs
new file mode 100644
--- /dev/null
+++ b/src/Choreography/Network/LocalMsgCount.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE GADTs #-}
+
+-- | This module defines an instrumented version of the `Local` backend,
+-- which counts how many `Send` operations are performed during execution of the choreography.
+-- It is used by the benchmarks in `bench/`.
+module Choreography.Network.LocalMsgCount where
+
+import Choreography.Location
+import Choreography.Network
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.Freer
+import Control.Monad.IO.Class
+import Data.HashMap.Strict (HashMap, (!))
+import Data.HashMap.Strict qualified as HashMap
+import Data.IORef
+
+-- | Each location is associated with a message buffer which stores messages sent
+-- from other locations.
+type MsgBuf = HashMap LocTm (Chan String)
+
+data LocalMsgCountConfig = LocalMsgCountConfig
+  { msgCounter :: IORef Int
+  , locToBuf :: HashMap LocTm MsgBuf
+  }
+
+newEmptyMsgBuf :: [LocTm] -> IO MsgBuf
+newEmptyMsgBuf = foldM f HashMap.empty
+  where
+    f hash loc = do
+      chan <- newChan
+      return (HashMap.insert loc chan hash)
+
+mkLocalMsgCountConfig :: IORef Int -> [LocTm] -> IO LocalMsgCountConfig
+mkLocalMsgCountConfig msgCounter locs = LocalMsgCountConfig msgCounter <$> foldM f HashMap.empty locs
+  where
+    f hash loc = do
+      buf <- newEmptyMsgBuf locs
+      return (HashMap.insert loc buf hash)
+
+locsLocalMsgCount :: LocalMsgCountConfig -> [LocTm]
+locsLocalMsgCount = HashMap.keys . locToBuf
+
+runNetworkLocalMsgCount :: MonadIO m => LocalMsgCountConfig -> LocTm -> Network m a -> m a
+runNetworkLocalMsgCount cfg self prog = interpFreer handler prog
+  where
+    handler :: MonadIO m => NetworkSig m a -> m a
+    handler (Run m)      = m
+    handler (Send a l)   = liftIO $ do
+      atomicModifyIORef' (msgCounter cfg) (\c -> (c + 1, ()))
+      writeChan ((locToBuf cfg ! l) ! self) (show a)
+    handler (Recv l)     = liftIO $ read <$> readChan ((locToBuf cfg ! self) ! l)
+    handler (BCast a ls) = mapM_ handler $ fmap (Send a) ls
+
+instance Backend LocalMsgCountConfig where
+  locs = locsLocalMsgCount
+  runNetwork = runNetworkLocalMsgCount
diff --git a/src/Control/Monad/Freer.hs b/src/Control/Monad/Freer.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/Freer.hs
@@ -0,0 +1,43 @@
+-- | This module defines the freer monad `Freer`, which allows manipulating
+-- effectful computations algebraically.
+module Control.Monad.Freer where
+
+import Control.Monad ((>=>))
+
+-- | Freer monads.
+--
+-- A freer monad @Freer f a@ represents an effectful computation that returns a
+-- value of type @a@. The parameter @f :: * -> *@ is a effect signature that
+-- defines the effectful operations allowed in the computation. @Freer f a@ is
+-- called a freer monad in that it's a `Monad` given any @f@.
+data Freer f a where
+  -- | A pure computation.
+  Return :: a -> Freer f a
+  -- | An effectful computation where the first argument @f b@ is the effect
+  -- to perform and returns a result of type @b@; the second argument
+  -- @b -> Freer f a@ is a continuation that specifies the rest of the
+  -- computation given the result of the performed effect.
+  Do :: f b -> (b -> Freer f a) -> Freer f a
+
+instance Functor (Freer f) where
+  fmap f (Return a) = Return (f a)
+  fmap f (Do eff k) = Do eff (fmap f . k)
+
+instance Applicative (Freer f) where
+  pure = Return
+
+  (Return f) <*> a = fmap f a
+  (Do eff k) <*> a = Do eff $ (<*> a) . k
+
+instance Monad (Freer f) where
+  (Return a) >>= f = f a
+  (Do eff k) >>= f = Do eff (k >=> f)
+
+-- | Lift an effect into the freer monad.
+toFreer :: f a -> Freer f a
+toFreer eff = Do eff Return
+
+-- | Interpret the effects in a freer monad in terms of another monad.
+interpFreer :: Monad m => (forall a. f a -> m a) -> Freer f a -> m a
+interpFreer _handler (Return a) = return a
+interpFreer handler (Do eff k) = handler eff >>= interpFreer handler . k
