diff --git a/.github/workflows/build-develop.yml b/.github/workflows/build-develop.yml
new file mode 100644
index 0000000..ead9353
--- /dev/null
+++ b/.github/workflows/build-develop.yml
@@ -0,0 +1,16 @@
+name: Build the develop version using Spack
+
+on:
+  workflow_dispatch: {}
+  schedule:
+  - cron: "0 0 * * *"
+
+jobs:
+  build:
+    runs-on: ubuntu-22.04
+    name: Build the main version of the package
+    steps:
+    - uses: actions/checkout@v4
+    - uses: mochi-hpc/mochi-test-build-action@v1
+      with:
+        package-version: "@develop"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 62dee22..ce18686 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,7 +16,7 @@ jobs:
       packages: write
     steps:
     - name: Checkout code
-      uses: actions/checkout@v2
+      uses: actions/checkout@v4
 
     - name: Setup spack
       uses: spack/setup-spack@v2.1.1
@@ -44,9 +44,6 @@ jobs:
         cmake .. -DENABLE_TESTS=ON \
                  -DENABLE_EXAMPLES=ON \
                  -DENABLE_MPI=ON \
-                 -DENABLE_MONA=ON \
-                 -DENABLE_SSG=ON \
-                 -DENABLE_ABT_IO=ON \
                  -DENABLE_PYTHON=ON \
                  -DCMAKE_BUILD_TYPE=Debug
         make
@@ -65,9 +62,6 @@ jobs:
                  -DENABLE_TESTS=ON \
                  -DENABLE_EXAMPLES=ON \
                  -DENABLE_MPI=ON \
-                 -DENABLE_MONA=ON \
-                 -DENABLE_SSG=ON \
-                 -DENABLE_ABT_IO=ON \
                  -DENABLE_PYTHON=ON \
                  -DCMAKE_BUILD_TYPE=Debug
         make
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3b74d01..fa41290 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@
 # See COPYRIGHT in top-level directory.
 cmake_minimum_required (VERSION 3.8)
 project (bedrock C CXX)
-set (CMAKE_CXX_STANDARD 14)
+set (CMAKE_CXX_STANDARD 17)
 set (CMAKE_CXX_STANDARD_REQUIRED ON)
 enable_testing ()
 
@@ -11,10 +11,7 @@ option (ENABLE_COVERAGE "Enable code coverage" OFF)
 option (ENABLE_TESTS    "Build tests" OFF)
 option (ENABLE_EXAMPLES "Build examples" OFF)
 option (ENABLE_MPI      "Enable MPI support" OFF)
-option (ENABLE_MONA     "Enable MoNA support" OFF)
-option (ENABLE_SSG      "Enable SSG support" OFF)
 option (ENABLE_FLOCK    "Enable Flock support" OFF)
-option (ENABLE_ABT_IO   "Enable ABT-IO support" OFF)
 option (ENABLE_PYTHON   "Enable Python support" OFF)
 
 # library version set here (e.g. for shared libs).
@@ -59,8 +56,6 @@ endif ()
 
 # search for bedrock-module-api
 find_package (bedrock-module-api REQUIRED)
-# search for pkg-config
-find_package (PkgConfig REQUIRED)
 # search fo thallium
 find_package (thallium REQUIRED)
 # search for nlohmann/json and schema validator
@@ -75,20 +70,6 @@ find_package (fmt REQUIRED)
 # search for toml11
 find_package (toml11 REQUIRED)
 # search for abt-io
-if (ENABLE_ABT_IO)
-    pkg_check_modules (ABTIO REQUIRED IMPORTED_TARGET abt-io)
-    add_definitions (-DENABLE_ABT_IO)
-    set (OPTIONAL_ABT_IO PkgConfig::ABTIO)
-    set (OPTIONAL_SERVER_DEPS "${OPTIONAL_SERVER_DEPS} abt-io")
-endif ()
-# search for ssg
-if (ENABLE_SSG)
-    pkg_check_modules (SSG REQUIRED IMPORTED_TARGET ssg)
-    add_definitions (-DENABLE_SSG)
-    set (OPTIONAL_SSG PkgConfig::SSG)
-    set (OPTIONAL_SERVER_DEPS "${OPTIONAL_SERVER_DEPS} ssg")
-    set (OPTIONAL_CLIENT_DEPS "${OPTIONAL_CLIENT_DEPS} ssg")
-endif ()
 if (ENABLE_FLOCK)
     find_package (flock REQUIRED)
     add_definitions (-DENABLE_FLOCK)
@@ -102,13 +83,6 @@ if (${ENABLE_MPI})
     add_definitions (-DENABLE_MPI)
     set (OPTIONAL_MPI MPI::MPI_C)
 endif ()
-# search for mona
-if (ENABLE_MONA)
-    pkg_check_modules (MONA REQUIRED IMPORTED_TARGET mona)
-    add_definitions (-DENABLE_MONA)
-    set (OPTIONAL_MONA PkgConfig::MONA)
-    set (OPTIONAL_SERVER_DEPS "${OPTIONAL_SERVER_DEPS} mona")
-endif ()
 # search for Python
 if (ENABLE_PYTHON)
     set (Python3_FIND_STRATEGY LOCATION)
diff --git a/bin/bedrock-query.cpp b/bin/bedrock-query.cpp
index 3d56325..376a633 100644
--- a/bin/bedrock-query.cpp
+++ b/bin/bedrock-query.cpp
@@ -1,8 +1,5 @@
 #include <bedrock/Client.hpp>
 #include <bedrock/ServiceGroupHandle.hpp>
-#ifdef ENABLE_SSG
-#include <ssg.h>
-#endif
 #include <spdlog/spdlog.h>
 #include <tclap/CmdLine.h>
 #include <nlohmann/json.hpp>
@@ -20,7 +17,6 @@ using nlohmann::json;
 static std::string              g_protocol;
 static std::vector<std::string> g_addresses;
 static std::string              g_log_level;
-static std::string              g_ssg_file;
 static std::string              g_flock_file;
 static std::string              g_jx9_file;
 static std::string              g_jx9_script_content;
@@ -47,10 +43,6 @@ int main(int argc, char** argv) {
     try {
         auto engine = thallium::engine(g_protocol, THALLIUM_CLIENT_MODE);
 
-#ifdef ENABLE_SSG
-        ssg_init();
-#endif
-
         if(!g_flock_file.empty()) {
             json flock_file_content;
             std::ifstream flock_file{g_flock_file};
@@ -70,8 +62,7 @@ int main(int argc, char** argv) {
 
         bedrock::Client client(engine);
 
-        auto sgh = g_ssg_file.empty() ? client.makeServiceGroupHandle(g_addresses, g_provider_id)
-                                      : client.makeServiceGroupHandleFromSSGFile(g_ssg_file, g_provider_id);
+        auto sgh = client.makeServiceGroupHandle(g_addresses, g_provider_id);
 
         std::string result;
         if (g_jx9_script_content.empty())
@@ -79,9 +70,6 @@ int main(int argc, char** argv) {
         else
             sgh.queryConfig(g_jx9_script_content, &result);
         std::cout << (g_pretty ? json::parse(result).dump(4) : result) << std::endl;
-#ifdef ENABLE_SSG
-        ssg_finalize();
-#endif
     } catch (const std::exception& e) { spdlog::critical(e.what()); exit(-1); }
     return 0;
 }
@@ -106,12 +94,6 @@ static void parseCommandLine(int argc, char** argv) {
             "f", "flock-file",
             "Flock file from which to read addresses of Bedrock daemons", false,
             "", "filename");
-#ifdef ENABLE_SSG
-        TCLAP::ValueArg<std::string> ssgFile(
-            "s", "ssg-file",
-            "SSG file from which to read addresses of Bedrock daemons", false,
-            "", "filename");
-#endif
         TCLAP::ValueArg<std::string> jx9File(
             "j", "jx9-file", "Jx9 file to send to processes and execute", false,
             "", "filename");
@@ -122,9 +104,6 @@ static void parseCommandLine(int argc, char** argv) {
         cmd.add(protocol);
         cmd.add(logLevel);
         cmd.add(flockFile);
-#ifdef ENABLE_SSG
-        cmd.add(ssgFile);
-#endif
         cmd.add(providerID);
         cmd.add(addresses);
         cmd.add(prettyJSON);
@@ -133,15 +112,12 @@ static void parseCommandLine(int argc, char** argv) {
         g_addresses   = addresses.getValue();
         g_log_level   = logLevel.getValue();
         g_flock_file  = flockFile.getValue();
-#ifdef ENABLE_SSG
-        g_ssg_file    = ssgFile.getValue();
-#endif
         g_protocol    = protocol.getValue();
         g_provider_id = providerID.getValue();
         g_pretty      = prettyJSON.getValue();
         g_jx9_file    = jx9File.getValue();
-        if(!g_addresses.empty() && !g_ssg_file.empty()) {
-            std::cerr << "error: specifying -s and -a at the same time is not supported" << std::endl;
+        if(g_addresses.empty()) {
+            std::cerr << "error: no address specified" << std::endl;
             exit(-1);
         }
     } catch (TCLAP::ArgException& e) {
diff --git a/build.sh b/build.sh
index 95bace3..c13ae14 100755
--- a/build.sh
+++ b/build.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 SCRIPT_DIR=$(dirname "$0")
-cmake $SCRIPT_DIR -DENABLE_TESTS=ON -DENABLE_EXAMPLES=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_PYTHON=ON -DENABLE_MPI=ON -DENABLE_MONA=ON -DENABLE_SSG=ON -DENABLE_ABT_IO=ON -DENABLE_FLOCK=ON
+cmake $SCRIPT_DIR -DENABLE_TESTS=ON -DENABLE_EXAMPLES=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_PYTHON=ON -DENABLE_MPI=ON -DENABLE_FLOCK=ON
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 0c52dcc..ee478a8 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,7 +1,7 @@
 add_executable (example-client ${CMAKE_CURRENT_SOURCE_DIR}/client.cpp)
 target_link_libraries (example-client bedrock-client)
 
-add_library (example-module-a ${CMAKE_CURRENT_SOURCE_DIR}/module-a.c)
+add_library (example-module-a ${CMAKE_CURRENT_SOURCE_DIR}/module-a.cpp)
 target_include_directories (example-module-a PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
 target_link_libraries (example-module-a bedrock::module-api)
 
diff --git a/examples/module-a.c b/examples/module-a.c
deleted file mode 100644
index 958360d..0000000
--- a/examples/module-a.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#include <bedrock/module.h>
-#include <stdlib.h>
-#include <string.h>
-
-static int module_a_register_provider(
-        bedrock_args_t args,
-        bedrock_module_provider_t* provider)
-{
-    margo_instance_id mid = bedrock_args_get_margo_instance(args);
-    uint16_t provider_id  = bedrock_args_get_provider_id(args);
-    ABT_pool pool         = bedrock_args_get_pool(args);
-    const char* config    = bedrock_args_get_config(args);
-    const char* name      = bedrock_args_get_name(args);
-
-    *provider = strdup("module_a:provider");
-    printf("Registered a provider from module A\n");
-    printf(" -> mid         = %p\n", (void*)mid);
-    printf(" -> provider id = %d\n", provider_id);
-    printf(" -> pool        = %p\n", (void*)pool);
-    printf(" -> config      = %s\n", config);
-    printf(" -> name        = %s\n", name);
-    return BEDROCK_SUCCESS;
-}
-
-static int module_a_deregister_provider(
-        bedrock_module_provider_t provider)
-{
-    free(provider);
-    printf("Deregistered a provider from module A\n");
-    return BEDROCK_SUCCESS;
-}
-
-static char* module_a_get_provider_config(
-        bedrock_module_provider_t provider) {
-    (void)provider;
-    return strdup("{}");
-}
-
-static int module_a_init_client(
-        bedrock_args_t args,
-        bedrock_module_client_t* client)
-{
-    margo_instance_id mid = bedrock_args_get_margo_instance(args);
-    *client = strdup("module_a:client");
-    printf("Registered a client from module A\n");
-    printf(" -> mid = %p\n", (void*)mid);
-    return BEDROCK_SUCCESS;
-}
-
-static int module_a_finalize_client(
-        bedrock_module_client_t client)
-{
-    free(client);
-    printf("Finalized a client from module A\n");
-    return BEDROCK_SUCCESS;
-}
-
-static char* module_a_get_client_config(
-        bedrock_module_provider_t provider) {
-    (void)provider;
-    return strdup("{}");
-}
-
-static int module_a_create_provider_handle(
-        bedrock_module_client_t client,
-        hg_addr_t address,
-        uint16_t provider_id,
-        bedrock_module_provider_handle_t* ph)
-{
-    (void)client;
-    (void)address;
-    (void)provider_id;
-    *ph = strdup("module_a:provider_handle");
-    printf("Created provider handle from module A\n");
-    return BEDROCK_SUCCESS;
-}
-
-static int module_a_destroy_provider_handle(
-        bedrock_module_provider_handle_t ph)
-{
-    free(ph);
-    printf("Destroyed provider handle from module A\n");
-    return BEDROCK_SUCCESS;
-}
-
-static struct bedrock_dependency client_dependencies[] = {
-    { "some_group", "ssg", BEDROCK_OPTIONAL },
-    BEDROCK_NO_MORE_DEPENDENCIES
-};
-
-static struct bedrock_module_v1 module_a = {
-    .api_version             = 1,
-    .register_provider       = module_a_register_provider,
-    .deregister_provider     = module_a_deregister_provider,
-    .get_provider_config     = module_a_get_provider_config,
-    .init_client             = module_a_init_client,
-    .finalize_client         = module_a_finalize_client,
-    .get_client_config       = module_a_get_client_config,
-    .create_provider_handle  = module_a_create_provider_handle,
-    .destroy_provider_handle = module_a_destroy_provider_handle,
-    .provider_dependencies   = NULL,
-    .client_dependencies     = client_dependencies
-};
-
-BEDROCK_REGISTER_MODULE_WITH_VERSION(module_a, module_a, 1)
diff --git a/examples/module-a.cpp b/examples/module-a.cpp
new file mode 100644
index 0000000..3457c7e
--- /dev/null
+++ b/examples/module-a.cpp
@@ -0,0 +1,53 @@
+/*
+ * (C) 2020 The University of Chicago
+ *
+ * See COPYRIGHT in top-level directory.
+ */
+#include <bedrock/AbstractComponent.hpp>
+#include <iostream>
+
+struct ActualProviderA {};
+
+class ComponentA : public bedrock::AbstractComponent {
+
+    ActualProviderA* m_provider = nullptr;
+
+    public:
+
+    ComponentA()
+    : m_provider{new ActualProviderA{}} {}
+
+    ~ComponentA() {
+        delete m_provider;
+    }
+
+    static std::shared_ptr<bedrock::AbstractComponent>
+        Register(const bedrock::ComponentArgs& args) {
+            std::cout << "Registering a ComponentA" << std::endl;
+            std::cout << " -> mid = " << (void*)args.engine.get_margo_instance() << std::endl;
+            std::cout << " -> provider id = " << args.provider_id << std::endl;
+            std::cout << " -> config = " << args.config << std::endl;
+            std::cout << " -> name = " << args.name << std::endl;
+            std::cout << " -> tags = ";
+            for(auto& t : args.tags) std::cout << t << " ";
+            std::cout << std::endl;
+            auto pool_it = args.dependencies.find("pool");
+            auto pool = pool_it->second[0]->getHandle<thallium::pool>();
+            return std::make_shared<ComponentA>();
+    }
+
+    static std::vector<bedrock::Dependency>
+        GetDependencies(const bedrock::ComponentArgs& args) {
+        (void)args;
+        std::vector<bedrock::Dependency> deps = {
+            { "pool", "pool", true, false, false }
+        };
+        return deps;
+    }
+
+    void* getHandle() override {
+        return static_cast<void*>(m_provider);
+    }
+};
+
+BEDROCK_REGISTER_COMPONENT_TYPE(module_a, ComponentA)
diff --git a/examples/module-b.cpp b/examples/module-b.cpp
index b815fa8..c6f019d 100644
--- a/examples/module-b.cpp
+++ b/examples/module-b.cpp
@@ -3,87 +3,67 @@
  *
  * See COPYRIGHT in top-level directory.
  */
-#include <bedrock/AbstractServiceFactory.hpp>
+#include <bedrock/AbstractComponent.hpp>
 #include <iostream>
 
-class ServiceBFactory : public bedrock::AbstractServiceFactory {
+struct ActualProviderA;
 
-    public:
-
-    ServiceBFactory() {
-        m_provider_dependencies.push_back({
-            "ssg_group", "ssg", BEDROCK_REQUIRED });
-        m_provider_dependencies.push_back({
-            "mona_instance", "mona", BEDROCK_REQUIRED });
-        m_provider_dependencies.push_back({
-            "a_provider", "module_a", BEDROCK_REQUIRED | BEDROCK_KIND_PROVIDER});
-        m_provider_dependencies.push_back({
-            "a_provider_handle", "module_a", BEDROCK_REQUIRED | BEDROCK_ARRAY | BEDROCK_KIND_PROVIDER_HANDLE});
-        m_provider_dependencies.push_back({
-            "a_client", "module_a", BEDROCK_REQUIRED | BEDROCK_KIND_CLIENT});
-    }
-
-    void* registerProvider(const bedrock::FactoryArgs& args) override {
-        std::cout << "Registering a provider from module B" << std::endl;
-        std::cout << " -> mid         = " << (void*)args.mid << std::endl;
-        std::cout << " -> provider_id = " << args.provider_id << std::endl;
-        std::cout << " -> pool        = " << (void*)args.pool << std::endl;
-        std::cout << " -> config      = " << args.config << std::endl;
-        std::cout << " -> name        = " << args.name << std::endl;
-        return (void*)0x1;
-    }
-
-    void deregisterProvider(void* provider) override {
-        (void)provider;
-        std::cout << "Deregistring provider from module B" << std::endl;
-    }
+struct ActualProviderB {};
 
-    std::string getProviderConfig(void* provider) override {
-        (void)provider;
-        return "{}";
-    }
+class ComponentB : public bedrock::AbstractComponent {
 
-    void* initClient(const bedrock::FactoryArgs& args) override {
-        (void)args;
-        std::cout << "Initializing client from module B" << std::endl;
-        return (void*)0x2;
-    }
+    ActualProviderB* m_provider = nullptr;
 
-    void finalizeClient(void* client) override {
-        (void)client;
-        std::cout << "Finalizing client from module B" << std::endl;
-    }
+    public:
 
-    std::string getClientConfig(void* client) override {
-        (void)client;
-        return "{}";
-    }
+    ComponentB()
+    : m_provider{new ActualProviderB{}} {}
 
-    void* createProviderHandle(void* client, hg_addr_t address, uint16_t provider_id) override {
-        (void)client;
-        (void)address;
-        (void)provider_id;
-        std::cout << "Creating a provider handle from module B" << std::endl;
-        return (void*)0x3;
+    ~ComponentB() {
+        delete m_provider;
     }
 
-    void destroyProviderHandle(void* providerHandle) override {
-        (void)providerHandle;
-        std::cout << "Destroying provider handle from module B" << std::endl;
+    static std::shared_ptr<bedrock::AbstractComponent>
+        Register(const bedrock::ComponentArgs& args) {
+            std::cout << "Registering a ComponentA" << std::endl;
+            std::cout << " -> mid = " << (void*)args.engine.get_margo_instance() << std::endl;
+            std::cout << " -> provider id = " << args.provider_id << std::endl;
+            std::cout << " -> config = " << args.config << std::endl;
+            std::cout << " -> name = " << args.name << std::endl;
+            std::cout << " -> tags = ";
+            for(auto& t : args.tags) std::cout << t << " ";
+            std::cout << std::endl;
+            auto pool_it = args.dependencies.find("pool");
+            auto pool = pool_it->second[0]->getHandle<thallium::pool>();
+            std::cout << " -> pool = " << pool.native_handle() << std::endl;
+            auto a_provider_it = args.dependencies.find("a_provider");
+            auto a_provider_comp_handle = a_provider_it->second[0]->getHandle<bedrock::ComponentPtr>();
+            auto a_provider = a_provider_comp_handle->getHandle();
+            std::cout << " -> a_provider = " << a_provider << std::endl;
+            auto a_ph_it = args.dependencies.find("a_provider_handles");
+            int i = 0;
+            for(auto& p : a_ph_it->second) {
+                auto ph = p->getHandle<thallium::provider_handle>();
+                std::cout << " -> a_provider_handles[" << i << "] = " <<
+                    static_cast<std::string>(ph) << " with provider id " << ph.provider_id() << std::endl;
+            }
+            return std::make_shared<ComponentB>();
     }
 
-    const std::vector<bedrock::Dependency>& getProviderDependencies() override {
-        return m_provider_dependencies;
+    static std::vector<bedrock::Dependency>
+        GetDependencies(const bedrock::ComponentArgs& args) {
+        (void)args;
+        std::vector<bedrock::Dependency> deps = {
+            { "pool", "pool", true, false, false },
+            { "a_provider", "module_a", true, false, false },
+            { "a_provider_handles", "module_a", false, true, false }
+        };
+        return deps;
     }
 
-    const std::vector<bedrock::Dependency>& getClientDependencies() override {
-        return m_client_dependencies;
+    void* getHandle() override {
+        return static_cast<void*>(m_provider);
     }
-
-    private:
-
-    std::vector<bedrock::Dependency> m_provider_dependencies;
-    std::vector<bedrock::Dependency> m_client_dependencies;
 };
 
-BEDROCK_REGISTER_MODULE_FACTORY(module_b, ServiceBFactory)
+BEDROCK_REGISTER_COMPONENT_TYPE(module_a, ComponentB)
diff --git a/include/bedrock/ABTioManager.hpp b/include/bedrock/ABTioManager.hpp
deleted file mode 100644
index ae2df38..0000000
--- a/include/bedrock/ABTioManager.hpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_ABTIO_MANAGER_HPP
-#define __BEDROCK_ABTIO_MANAGER_HPP
-
-#include <thallium.hpp>
-#include <nlohmann/json.hpp>
-#include <memory>
-#include <bedrock/NamedDependency.hpp>
-
-/* Forward declaration to avoid including <abt-io.h> */
-typedef struct abt_io_instance* abt_io_instance_id;
-
-namespace bedrock {
-
-namespace tl = thallium;
-
-class Server;
-class ServerImpl;
-class MargoManager;
-class DependencyFinder;
-class ABTioManagerImpl;
-class Jx9Manager;
-
-/**
- * @brief The ABTioManager class is encapsulates multiple ABT-IO instances.
- */
-class ABTioManager {
-
-    friend class Server;
-    friend class ServerImpl;
-    friend class DependencyFinder;
-
-    using json = nlohmann::json;
-
-  public:
-    /**
-     * @brief Constructor from a configuration string. The configuration
-     * should be a JSON array listing ABT-IO instances in the following
-     * format:
-     * [ ... { "name" : "some_name", "pool" : "some_pool" }, ... ]
-     *
-     * @param margo_ctx Margo context.
-     * @param configString JSON configuration string.
-     */
-    ABTioManager(const MargoManager& margo_ctx,
-                 const Jx9Manager& jx9,
-                 const json& config);
-
-    /**
-     * @brief Copy-constructor.
-     */
-    ABTioManager(const ABTioManager&);
-
-    /**
-     * @brief Move-constructor.
-     */
-    ABTioManager(ABTioManager&&);
-
-    /**
-     * @brief Copy-assignment operator.
-     */
-    ABTioManager& operator=(const ABTioManager&);
-
-    /**
-     * @brief Move-assignment operator.
-     */
-    ABTioManager& operator=(ABTioManager&&);
-
-    /**
-     * @brief Destructor.
-     */
-    ~ABTioManager();
-
-    /**
-     * @brief Checks whether the ABTioManager instance is valid.
-     */
-    operator bool() const;
-
-    /**
-     * @brief Get an internal abt-io instance by its name.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the ABT-IO instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getABTioInstance(const std::string& name) const;
-
-    /**
-     * @brief Get an internal abt-io instance by its index.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the ABT-IO instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getABTioInstance(size_t index) const;
-
-    /**
-     * @brief Returns the number of abt-io instances stored.
-     */
-    size_t numABTioInstances() const;
-
-    /**
-     * @brief Adds an ABT-IO instance.
-     *
-     * @param name Name of the instance.
-     * @param pool Name of the pool to use.
-     * @param config JSON configuration.
-     *
-     * @return the NamedDependency handle for the newly-created instance.
-     */
-    std::shared_ptr<NamedDependency>
-        addABTioInstance(const std::string&                      name,
-                         const std::shared_ptr<NamedDependency>& pool = {},
-                         const json&                             config = {});
-
-
-    /**
-     * @brief Adds an ABT-IO istance as described by the
-     * provided JSON object.
-     *
-     * @param description ABT-IO instance description.
-     *
-     * @return the NamedDependency handle for the newly-created instance.
-     *
-     * Example of JSON description:
-     *
-     * ```json
-     * {
-     *    "name": "my_abt_io_instance",
-     *    "pool": "my_pool",
-     *    "config": {}
-     * }
-     * ```
-     */
-    std::shared_ptr<NamedDependency>
-        addABTioInstanceFromJSON(const json& description);
-
-    /**
-     * @brief Return the current JSON configuration.
-     */
-    json getCurrentConfig() const;
-
-  private:
-    std::shared_ptr<ABTioManagerImpl> self;
-
-    inline operator std::shared_ptr<ABTioManagerImpl>() const { return self; }
-
-    inline ABTioManager(std::shared_ptr<ABTioManagerImpl> impl)
-    : self(std::move(impl)) {}
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/include/bedrock/Client.hpp b/include/bedrock/Client.hpp
index a18cb02..bcef983 100644
--- a/include/bedrock/Client.hpp
+++ b/include/bedrock/Client.hpp
@@ -92,34 +92,6 @@ class Client {
             const std::string& address,
             uint16_t           provider_id=0) const;
 
-    /**
-     * @brief Creates a handle to a group of Bedrock processes
-     * from an SSG group file.
-     *
-     * @important SSG needs to have been initialized for this
-     * function to work, otherwise an exception will be returned.
-     *
-     * @param groupfile SSG group file.
-     * @param provider_id Provider ID of the bedrock providers.
-     *
-     * @return ServiceGroupHandle instance.
-     */
-    ServiceGroupHandle makeServiceGroupHandleFromSSGFile(
-            const std::string& groupfile,
-            uint16_t provider_id=0) const;
-
-    /**
-     * @brief Creates a handle to a group of Bedrock processes.
-     *
-     * @param gid Existing SSG group id.
-     * @param provider_id Provider ID of the bedrock providers.
-     *
-     * @return ServiceGroupHandle instance.
-     */
-    ServiceGroupHandle makeServiceGroupHandleFromSSGGroup(
-            uint64_t gid,
-            uint16_t provider_id=0) const;
-
     /**
      * @brief Creates a handle to a group of Bedrock processes
      * from an Flock group file.
diff --git a/include/bedrock/ClientDescriptor.hpp b/include/bedrock/ClientDescriptor.hpp
deleted file mode 100644
index c26698b..0000000
--- a/include/bedrock/ClientDescriptor.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_CLIENT_DESC_HPP
-#define __BEDROCK_CLIENT_DESC_HPP
-
-#include <string>
-#include <thallium/serialization/stl/string.hpp>
-
-namespace bedrock {
-
-class AbstractServiceFactory;
-
-/**
- * @brief A ClientDescriptor is an object that describes a client.
- */
-struct ClientDescriptor {
-
-    std::string name; // name of the client
-    std::string type; // name of the module
-
-    template <typename A> void serialize(A& ar) { ar(name, type); }
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/include/bedrock/ClientManager.hpp b/include/bedrock/ClientManager.hpp
deleted file mode 100644
index bdc971d..0000000
--- a/include/bedrock/ClientManager.hpp
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * (C) 2021 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_CLIENT_MANAGER_HPP
-#define __BEDROCK_CLIENT_MANAGER_HPP
-
-#include <bedrock/MargoManager.hpp>
-#include <bedrock/AbstractServiceFactory.hpp>
-#include <bedrock/ClientDescriptor.hpp>
-#include <bedrock/NamedDependency.hpp>
-#include <nlohmann/json.hpp>
-#include <string>
-#include <memory>
-
-namespace bedrock {
-
-class ClientManagerImpl;
-class Server;
-class ServerImpl;
-class DependencyFinder;
-class Jx9Manager;
-
-/**
- * @brief A ClientManager is a Bedrock provider that manages
- * a list of clients and uses the ModuleContext to create new ones
- * from configuration files.
- */
-class ClientManager {
-
-    friend class Server;
-    friend class DependencyFinder;
-    friend class ClientManagerImpl;
-    friend class ServerImpl;
-
-    using json = nlohmann::json;
-
-  public:
-    /**
-     * @brief Constructor.
-     *
-     * @param margo Margo context
-     * @param jx9 Jx9 manager
-     * @param provider_id Provider id used by this client manager
-     * @param pool Pool in which to execute RPCs looking up clients
-     */
-    ClientManager(const MargoManager& margo, const Jx9Manager& jx9,
-                  uint16_t provider_id,
-                  const std::shared_ptr<NamedDependency>& pool);
-
-    /**
-     * @brief Copy-constructor.
-     */
-    ClientManager(const ClientManager&);
-
-    /**
-     * @brief Move-constructor.
-     */
-    ClientManager(ClientManager&&);
-
-    /**
-     * @brief Copy-assignment operator.
-     */
-    ClientManager& operator=(const ClientManager&);
-
-    /**
-     * @brief Move-assignment operator.
-     */
-    ClientManager& operator=(ClientManager&&);
-
-    /**
-     * @brief Destructor.
-     */
-    ~ClientManager();
-
-    /**
-     * @brief Checks whether the ClientManager instance is valid.
-     */
-    operator bool() const;
-
-    /**
-     * @brief Set the DependencyFinder object to use to resolve dependencies.
-     *
-     * @param finder DependencyFinder
-     */
-    void setDependencyFinder(const DependencyFinder& finder);
-
-    /**
-     * @brief Get an internal client instance by its name.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the client instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getClient(const std::string& name) const;
-
-    /**
-     * @brief Get an internal client instance by its index.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the client instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getClient(size_t index) const;
-
-    /**
-     * @brief Find any client of a certain type. If none is found,
-     * create such a client. This function will throw an exception
-     * if the client could not be found nor created.
-     *
-     * @param [in] type Type of client to lookup
-     * @param [out] wrapper Resulting client wrapper
-     */
-    std::shared_ptr<NamedDependency>
-        getOrCreateAnonymous(const std::string& type);
-
-    /**
-     * @brief Return the number of clients.
-     */
-    size_t numClients() const;
-
-    /**
-     * @brief Create a client.
-     *
-     * @param name Name of the client.
-     * @param type Type of the client.
-     * @param config JSON configuration for the client.
-     * @param dependencies Dependency map.
-     * @param tags Tags.
-     */
-    std::shared_ptr<NamedDependency>
-        addClient(const std::string&           name,
-                  const std::string&           type,
-                  const json&                  config,
-                  const ResolvedDependencyMap& dependencies,
-                  const std::vector<std::string>& tags = {});
-
-    /**
-     * @brief Destroy a client.
-     *
-     * @param name Name of the client.
-     */
-    void removeClient(const std::string& name);
-
-    /**
-     * @brief Destroy a client.
-     *
-     * @param index Index..
-     */
-    void removeClient(size_t index);
-
-    /**
-     * @brief Add a client from a full JSON description. The description should
-     * be of the following form:
-     *
-     * {
-     *      "type" : "remi",
-     *      "name" : "my_remi_client",
-     *      "dependencies" : {
-     *          "abt_io" : "my_abt_io"
-     *      },
-     *      "config" : { ... },
-     *      "tage" : [ "tag1", "tag2", ... ]
-     *  }
-     *
-     * @param jsonString JSON string.
-     */
-    std::shared_ptr<NamedDependency>
-        addClientFromJSON(const json& description);
-
-    /**
-     * @brief Add a list of providers represented by a JSON array.
-     * The JSON array entries must follow the format expected by
-     * addClientFromJSON.
-     *
-     * @param jsonString JSON string.
-     */
-    void addClientListFromJSON(const json& list);
-
-    /**
-     * @brief Return the current JSON configuration.
-     */
-    json getCurrentConfig() const;
-
-  private:
-    std::shared_ptr<ClientManagerImpl> self;
-
-    inline operator std::shared_ptr<ClientManagerImpl>() const { return self; }
-
-    inline ClientManager(std::shared_ptr<ClientManagerImpl> impl)
-    : self(std::move(impl)) {}
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/include/bedrock/DependencyFinder.hpp b/include/bedrock/DependencyFinder.hpp
index 41988cc..8cd0b6e 100644
--- a/include/bedrock/DependencyFinder.hpp
+++ b/include/bedrock/DependencyFinder.hpp
@@ -7,10 +7,7 @@
 #define __BEDROCK_DEPENDENCY_RESOLVER_HPP
 
 #include <bedrock/MargoManager.hpp>
-#include <bedrock/ABTioManager.hpp>
-#include <bedrock/SSGManager.hpp>
 #include <bedrock/ProviderManager.hpp>
-#include <bedrock/ClientManager.hpp>
 #include <bedrock/MPIEnv.hpp>
 #include <string>
 #include <memory>
@@ -20,7 +17,6 @@ namespace bedrock {
 class Server;
 class ServerImpl;
 class ProviderManager;
-class ClientManager;
 class DependencyFinderImpl;
 
 /**
@@ -31,7 +27,6 @@ class DependencyFinder {
 
     friend class Server;
     friend class ProviderManager;
-    friend class ClientManager;
     friend class ServerImpl;
 
   public:
@@ -39,16 +34,11 @@ class DependencyFinder {
      * @brief Constructor.
      * @param mpi MPI context
      * @param margo Margo context
-     * @param abtio ABT-IO context
-     * @param ssg SSG context
      * @param pmanager Provider manager
-     * @param cmanager Client manager
      */
     DependencyFinder(const MPIEnv& mpi,
-                     const MargoManager& margo, const ABTioManager& abtio,
-                     const SSGManager& ssg, const MonaManager& mona,
-                     const ProviderManager& pmanager,
-                     const ClientManager& cmanager);
+                     const MargoManager& margo,
+                     const ProviderManager& pmanager);
 
     /**
      * @brief Copy-constructor.
@@ -93,7 +83,6 @@ class DependencyFinder {
      * SPECIFIER  := NAME
      *            |  TYPE ':' ID
      * LOCATION   := ADDRESS
-     *            |  'ssg://' NAME '/' RANK
      * ADDRESS    := <mercury address>
      * NAME       := <qualified identifier>
      * ID         := <provider id>
@@ -101,20 +90,34 @@ class DependencyFinder {
      *
      * For instance, "abc" represents the name "abc".
      * "abc:123" represents a provider of type "abc" with
-     * provider id 123. "abc->def@address" represents a provider handle
-     * created from client named "abc", pointing to a provider named "def"
-     * at address "address".
+     * provider id 123. "abc@address" represents a provider handle
+     * pointing to a provider named "abc" at address "address".
      *
      * @param [in] type Type of dependency.
-     * @param [in] kind Kind of dependency (BEDROCK_KIND_*).
      * @param [in] spec Specification string.
      * @param [out] Resolved specification.
      *
      * @return handle to dependency
      */
     std::shared_ptr<NamedDependency>
-        find(const std::string& type, int32_t kind,
-             const std::string& spec, std::string* resolved) const;
+        find(const std::string& type,
+             const std::string& spec,
+             std::string* resolved) const;
+
+    /**
+     * @brief Find a dependency by an "index" value. The dependency
+     * should be a pool or an xstream
+     *
+     * @param [in] type Type of dependency.
+     * @param [in] index Index of the dependency.
+     * @param [out] resolved Resolved specification.
+     *
+     * @return handle to dependency
+     */
+    std::shared_ptr<NamedDependency>
+        find(const std::string& type,
+             size_t index,
+             std::string* resolved) const;
 
     /**
      * @brief Find a local provider based on a type and provider id.
@@ -126,7 +129,8 @@ class DependencyFinder {
      * @return An abstract pointer to the dependency.
      */
     std::shared_ptr<NamedDependency> findProvider(
-            const std::string& type, uint16_t provider_id) const;
+            const std::string& type,
+            uint16_t provider_id) const;
 
     /**
      * @brief Find a local provider based on a name.
@@ -139,45 +143,25 @@ class DependencyFinder {
      * @return An abstract pointer to the dependency.
      */
     std::shared_ptr<NamedDependency> findProvider(
-            const std::string& type, const std::string& name,
+            const std::string& type,
+            const std::string& name,
             uint16_t* provider_id = nullptr) const;
 
-    /**
-     * @brief Find client with a given name. The returned
-     * handle will remain valid until the program terminates.
-     * If the name is empty, this function will try to find
-     * a client of the specified type.
-     *
-     * @param type Type of client.
-     * @param name Name of the client.
-     * @param create_if_not_found Create the client if not found.
-     * @param found_name name of the client found or create.
-     *
-     * @return An abstract pointer to the dependency.
-     */
-    std::shared_ptr<NamedDependency> findClient(
-            const std::string& type, const std::string& name) const;
-
     /**
      * @brief Make a provider handle to a specified provider.
      * Throws an exception if no provider was found with this
      * provider id at the specified location.
-     * The returned VoidPtr object owns the underlying provider
-     * handle, so the caller is responsible for copying it if
-     * necessary.
      *
-     * @param client_name Name of the client to use (or "" for any client of the
-     * right type)
      * @param type Type of service.
      * @param provider_id Provider id
-     * @param locator Location (e.g. "local" or mercury or ssg addresses)
+     * @param locator Location (e.g. "local" or mercury address)
      * @param resolved Output resolved specification
      *
      * @return An abstract pointer to the dependency.
      */
     std::shared_ptr<NamedDependency>
-        makeProviderHandle(const std::string& client_name,
-                           const std::string& type, uint16_t provider_id,
+        makeProviderHandle(const std::string& type,
+                           uint16_t provider_id,
                            const std::string& locator,
                            std::string* resolved) const;
 
@@ -185,22 +169,17 @@ class DependencyFinder {
      * @brief Make a provider handle to a specified provider.
      * Throws an exception if no provider was found with this
      * provider name at the specified location.
-     * The returned VoidPtr object owns the underlying provider
-     * handle, so the caller is responsible for copying it if
-     * necessary.
      *
-     * @param client_name Name of the client to use (or "" for any client of the
-     * right type)
      * @param type Type of service.
      * @param name Name of the provider
-     * @param locator Location (e.g. "local" or mercury or ssg addresses)
+     * @param locator Location (e.g. "local" or mercury addresses)
      * @param resolved Output resolved specification
      *
      * @return An abstract pointer to the dependency.
      */
     std::shared_ptr<NamedDependency> makeProviderHandle(
-            const std::string& client_name,
-            const std::string& type, const std::string& name,
+            const std::string& type,
+            const std::string& name,
             const std::string& locator,
             std::string* resolved) const;
 
diff --git a/include/bedrock/Jx9Manager.hpp b/include/bedrock/Jx9Manager.hpp
index b207db5..5156b02 100644
--- a/include/bedrock/Jx9Manager.hpp
+++ b/include/bedrock/Jx9Manager.hpp
@@ -17,11 +17,6 @@ namespace bedrock {
 class Server;
 class ServerImpl;
 class Jx9ManagerImpl;
-class ABTioManager;
-class SSGManager;
-class SSGManagerImpl;
-class MonaManager;
-class ClientManager;
 class ProviderManager;
 
 /**
@@ -31,11 +26,6 @@ class Jx9Manager {
 
     friend class Server;
     friend class ServerImpl;
-    friend class ABTioManager;
-    friend class SSGManager;
-    friend class SSGManagerImpl;
-    friend class MonaManager;
-    friend class ClientManager;
     friend class ProviderManager;
 
   public:
diff --git a/include/bedrock/MargoManager.hpp b/include/bedrock/MargoManager.hpp
index 695b02a..74bea4b 100644
--- a/include/bedrock/MargoManager.hpp
+++ b/include/bedrock/MargoManager.hpp
@@ -16,15 +16,8 @@ namespace tl = thallium;
 
 class Server;
 class ProviderManager;
-class ClientManager;
 class DependencyFinder;
 class MargoManagerImpl;
-class SSGManager;
-class SSGData;
-class ABTioManager;
-class ABTioEntry;
-class MonaManager;
-class MonaEntry;
 class ProviderEntry;
 class ServerImpl;
 
@@ -35,14 +28,7 @@ class MargoManager {
 
     friend class Server;
     friend class ProviderManager;
-    friend class ClientManager;
     friend class DependencyFinder;
-    friend class SSGManager;
-    friend class SSGEntry;
-    friend class MonaEntry;
-    friend class MonaManager;
-    friend class ABTioEntry;
-    friend class ABTioManager;
     friend class ProviderEntry;
     friend class ServerImpl;
 
@@ -106,7 +92,7 @@ class MargoManager {
      *
      * @return reference to the thallium engine.
      */
-    tl::engine getThalliumEngine() const;
+    const tl::engine& getThalliumEngine() const;
 
     /**
      * @brief Get the default handle pool from Margo.
diff --git a/include/bedrock/MonaManager.hpp b/include/bedrock/MonaManager.hpp
deleted file mode 100644
index 86007fd..0000000
--- a/include/bedrock/MonaManager.hpp
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * (C) 2022 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_MONA_MANAGER_HPP
-#define __BEDROCK_MONA_MANAGER_HPP
-
-#include <thallium.hpp>
-#include <memory>
-#include <bedrock/NamedDependency.hpp>
-#include <nlohmann/json.hpp>
-
-/* Forward declaration to avoid including <mona.h> */
-typedef struct mona_instance* mona_instance_t;
-
-namespace bedrock {
-
-namespace tl = thallium;
-
-class Server;
-class ServerImpl;
-class DependencyFinder;
-class MargoManager;
-class MonaManagerImpl;
-class Jx9Manager;
-
-/**
- * @brief The MonaManager class encapsulates multiple MoNA instances.
- */
-class MonaManager {
-
-    friend class Server;
-    friend class ServerImpl;
-    friend class DependencyFinder;
-
-    using json = nlohmann::json;
-
-  public:
-    /**
-     * @brief Constructor from a configuration string. The configuration
-     * should be a JSON array listing MoNA instances in the following
-     * format:
-     * [ ... { "name" : "some_name", "pool" : "some_pool", "address" : "some_address" }, ... ]
-     *
-     * @param margoMgr MargoManager
-     * @param jx9 JX9 manager
-     * @param config JSON configuration of the manager.
-     * @param defaultProtocol default protocol to use if not specified in the JSON.
-     */
-    MonaManager(const MargoManager& margo,
-                const Jx9Manager& jx9,
-                const json& config,
-                const std::string& defaultProtocol);
-
-    /**
-     * @brief Copy-constructor.
-     */
-    MonaManager(const MonaManager&);
-
-    /**
-     * @brief Move-constructor.
-     */
-    MonaManager(MonaManager&&);
-
-    /**
-     * @brief Copy-assignment operator.
-     */
-    MonaManager& operator=(const MonaManager&);
-
-    /**
-     * @brief Move-assignment operator.
-     */
-    MonaManager& operator=(MonaManager&&);
-
-    /**
-     * @brief Destructor.
-     */
-    ~MonaManager();
-
-    /**
-     * @brief Checks whether the MonaManager instance is valid.
-     */
-    operator bool() const;
-
-    /**
-     * @brief Get an internal Mona instance by its name.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the Mona instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getMonaInstance(const std::string& name) const;
-
-    /**
-     * @brief Get the internal mona_instance_t by its index.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the Mona instance.
-     */
-    std::shared_ptr<NamedDependency>
-        getMonaInstance(size_t index) const;
-
-    /**
-     * @brief Returns the number of mona instances stored.
-     */
-    size_t numMonaInstances() const;
-
-    /**
-     * @brief Adds a Mona instance.
-     *
-     * @param name Name of the instance.
-     * @param pool Name of the pool to use.
-     * @param address Address to use..
-     */
-    std::shared_ptr<NamedDependency>
-        addMonaInstance(const std::string& name,
-                        std::shared_ptr<NamedDependency> pool,
-                        const std::string& address);
-
-    /**
-     * @brief Adds a Mona instance from a JSON description.
-     *
-     * @param description JSON description.
-     *
-     * @return a NamedDependency representing the newly-created Mona instance.
-     */
-    std::shared_ptr<NamedDependency>
-        addMonaInstanceFromJSON(const json& description);
-
-    /**
-     * @brief Return the current JSON configuration.
-     */
-    json getCurrentConfig() const;
-
-  private:
-    std::shared_ptr<MonaManagerImpl> self;
-
-    inline operator std::shared_ptr<MonaManagerImpl>() const { return self; }
-
-    inline MonaManager(std::shared_ptr<MonaManagerImpl> impl)
-    : self(std::move(impl)) {}
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/include/bedrock/ProviderHandle.hpp b/include/bedrock/ProviderHandle.hpp
new file mode 100644
index 0000000..5115b6c
--- /dev/null
+++ b/include/bedrock/ProviderHandle.hpp
@@ -0,0 +1,22 @@
+/*
+ * (C) 2020 The University of Chicago
+ *
+ * See COPYRIGHT in top-level directory.
+ */
+#ifndef __BEDROCK_PROVIDER_HANDLE_H
+#define __BEDROCK_PROVIDER_HANDLE_H
+
+#include <thallium.hpp>
+
+namespace bedrock {
+
+/**
+ * @brief A pointer to a ProviderHandle is returned by
+ * DependencyFinder::makeProviderHandle, wrapped as a
+ * std::shared_ptr<NamedDependency>.
+ */
+using ProviderHandle = thallium::provider_handle;
+
+} // namespace bedrock
+
+#endif
diff --git a/include/bedrock/ProviderManager.hpp b/include/bedrock/ProviderManager.hpp
index 7f167a4..97965da 100644
--- a/include/bedrock/ProviderManager.hpp
+++ b/include/bedrock/ProviderManager.hpp
@@ -9,7 +9,6 @@
 #include <bedrock/NamedDependency.hpp>
 #include <bedrock/ProviderDescriptor.hpp>
 #include <bedrock/MargoManager.hpp>
-#include <bedrock/AbstractServiceFactory.hpp>
 #include <nlohmann/json.hpp>
 #include <string>
 #include <memory>
@@ -130,26 +129,6 @@ class ProviderManager {
     std::shared_ptr<ProviderDependency>
         lookupProvider(const std::string& spec) const;
 
-    /**
-     * @brief Register a provider from a descriptor.
-     *
-     * @param name Name of the provider.
-     * @param type Type of provider.
-     * @param provider_id Provider ID.
-     * @param pool Pool.
-     * @param config JSON configuration for the provider.
-     * @param dependencies Dependency map.
-     * @param tags Tags.
-     */
-    std::shared_ptr<ProviderDependency>
-        registerProvider(const std::string&               name,
-                         const std::string&               type,
-                         uint16_t                         provider_id,
-                         std::shared_ptr<NamedDependency> pool,
-                         const json&                      config,
-                         const ResolvedDependencyMap&     dependencies,
-                         const std::vector<std::string>&  tags = {});
-
     /**
      * @brief Deregister a provider from a specification. The specification has
      * the same format as in lookupProvider().
@@ -185,15 +164,6 @@ class ProviderManager {
      */
     void addProviderListFromJSON(const json& list);
 
-    /**
-     * @brief Change the pool associated with a provider.
-     *
-     * @param provider Provider.
-     * @param pool New pool.
-     */
-    void changeProviderPool(const std::string& provider,
-                            const std::string& pool);
-
     /**
      * @brief Migrates the specified provider state to the destination.
      *
diff --git a/include/bedrock/SSGManager.hpp b/include/bedrock/SSGManager.hpp
deleted file mode 100644
index edd29af..0000000
--- a/include/bedrock/SSGManager.hpp
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_SSG_MANAGER_HPP
-#define __BEDROCK_SSG_MANAGER_HPP
-
-#include <bedrock/MargoManager.hpp>
-#include <nlohmann/json.hpp>
-#include <string>
-#include <memory>
-
-typedef uint64_t ssg_group_id_t;
-typedef uint64_t ssg_member_id_t;
-typedef struct ssg_group_config ssg_group_config_t;
-
-namespace bedrock {
-
-class Server;
-class ServerImpl;
-class DependencyFinder;
-class SSGManagerImpl;
-class SSGUpdateHandler;
-class Jx9Manager;
-
-/**
- * @brief The SSGManager class encapsulates an ssg_group_id_t.
- */
-class SSGManager {
-
-    friend class Server;
-    friend class ServerImpl;
-    friend class DependencyFinder;
-    friend class SSGUpdateHandler;
-
-    using json = nlohmann::json;
-
-  public:
-    /**
-     * @brief Constructor from a JSON configurations string.
-     *
-     * @param margo MargoManager
-     * @param jx9 Jx9Manager
-     * @param config JSON configuration.
-     */
-    SSGManager(const MargoManager& margo,
-               const Jx9Manager& jx9,
-               const json& config = json::object());
-
-    /**
-     * @brief Copy-constructor.
-     */
-    SSGManager(const SSGManager&);
-
-    /**
-     * @brief Move-constructor.
-     */
-    SSGManager(SSGManager&&);
-
-    /**
-     * @brief Copy-assignment operator.
-     */
-    SSGManager& operator=(const SSGManager&);
-
-    /**
-     * @brief Move-assignment operator.
-     */
-    SSGManager& operator=(SSGManager&&);
-
-    /**
-     * @brief Destructor.
-     */
-    ~SSGManager();
-
-    /**
-     * @brief Checks whether the SSGManager instance is valid.
-     */
-    operator bool() const;
-
-    /**
-     * @brief Create a group from a JSON configuration.
-     *
-     * @param description JSON description of the group.
-     *
-     * @return The created group as a NamedDependency.
-     *
-     * Example of JSON description:
-     *
-     * ```json
-     * {
-     *    "name": "my_ssg_group",
-     *    "pool": "my_pool",
-     *    "credential": 1234,
-     *    "group_file": "/path/to/group/file.ssg",
-     *    "bootstrap": "init|join",
-     *    "swim": {
-     *        "period_length_ms": 500,
-     *        "suspect_timeout_periods": 3,
-     *        "subgroup_member_count": 5
-     *    }
-     * }
-     * ```
-     */
-    std::shared_ptr<NamedDependency>
-        addGroupFromJSON(const json& description);
-
-    /**
-     * @brief Create a group and add it to the SSG context.
-     *
-     * @param name Name of the group.
-     * @param config Configuration.
-     * @param pool Pool.
-     * @param bootstrap_method Bootstrap method.
-     * @param group_file Group file to create for other processes to join.
-     *
-     * @return The newly created SSG group.
-     */
-    std::shared_ptr<NamedDependency>
-        addGroup(const std::string&        name,
-                 const ssg_group_config_t& config,
-                 const std::shared_ptr<NamedDependency>& pool,
-                 const std::string& bootstrap_method,
-                 const std::string& group_file = "");
-
-    /**
-     * @brief Get an internal SSG group by its name.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the group.
-     */
-    std::shared_ptr<NamedDependency> getGroup(const std::string& group_name) const;
-
-    /**
-     * @brief Get an internal SSG group by its index.
-     * If not found, this function will throw an Exception.
-     * If returned, the shared_ptr is guaranteed not to be null.
-     *
-     * @return a NamedDependency representing the group.
-     */
-    std::shared_ptr<NamedDependency> getGroup(size_t index) const;
-
-    /**
-     * @brief Get the number of groups.
-     */
-    size_t getNumGroups() const;
-
-    /**
-     * @brief Resolve an address starting with ssg://
-     * These address may be:
-     * - ssg://<group-name>/members/<member-id>
-     * - ssg://<group-name>/ranks/<rank>
-     * This function will throw an exception if the address
-     * could not be resolved. The caller is NOT supposed to
-     * destroy the returned hg_addr_t.
-     *
-     * @param address Address
-     *
-     * @return the corresponding hg_addr_t.
-     */
-    hg_addr_t resolveAddress(const std::string& address) const;
-
-    /**
-     * @brief Return the current JSON configuration.
-     */
-    json getCurrentConfig() const;
-
-  private:
-    std::shared_ptr<SSGManagerImpl> self;
-
-    inline operator std::shared_ptr<SSGManagerImpl>() const { return self; }
-
-    inline SSGManager(std::shared_ptr<SSGManagerImpl> impl)
-    : self(std::move(impl)) {}
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/include/bedrock/Server.hpp b/include/bedrock/Server.hpp
index f9fbbd5..ec2e610 100644
--- a/include/bedrock/Server.hpp
+++ b/include/bedrock/Server.hpp
@@ -7,10 +7,7 @@
 #define __BEDROCK_SERVER_HPP
 
 #include <bedrock/MargoManager.hpp>
-#include <bedrock/ABTioManager.hpp>
-#include <bedrock/SSGManager.hpp>
 #include <bedrock/ProviderManager.hpp>
-#include <bedrock/ClientManager.hpp>
 #include <thallium.hpp>
 #include <memory>
 
@@ -85,26 +82,11 @@ class Server {
      */
     MargoManager getMargoManager() const;
 
-    /**
-     * @brief Get the underlying ABTioManager.
-     */
-    ABTioManager getABTioManager() const;
-
     /**
      * @brief Get the underlying ProviderManager.
      */
     ProviderManager getProviderManager() const;
 
-    /**
-     * @brief Get the underlying ClientManager.
-     */
-    ClientManager getClientManager() const;
-
-    /**
-     * @brief Get the underlying SSG context.
-     */
-    SSGManager getSSGManager() const;
-
     /**
      * @brief Blocks until the underlying margo instance is finalized.
      */
@@ -123,8 +105,8 @@ class Server {
   private:
     std::unique_ptr<ServerImpl> self;
 
-    static void onFinalize(void* uargs);
-    static void onPreFinalize(void* uargs);
+    void onFinalize();
+    void onPreFinalize();
 };
 
 } // namespace bedrock
diff --git a/include/bedrock/ServiceHandle.hpp b/include/bedrock/ServiceHandle.hpp
index a67173d..e0a742a 100644
--- a/include/bedrock/ServiceHandle.hpp
+++ b/include/bedrock/ServiceHandle.hpp
@@ -80,11 +80,10 @@ class ServiceHandle {
     /**
      * @brief Ask the remote service daemon to load a module library.
      *
-     * @param name Name of the module.
      * @param path Library path.
      * @param req Asynchronous request to wait on, if provided.
      */
-    void loadModule(const std::string& name, const std::string& path,
+    void loadModule(const std::string& path,
                     AsyncRequest* req = nullptr) const;
 
     /**
@@ -171,26 +170,6 @@ class ServiceHandle {
     void addClient(const std::string& description,
                    AsyncRequest*      req = nullptr) const;
 
-    /**
-     * @brief Creates a new ABT-IO instance on the target service daemon.
-     *
-     * @param description JSON description.
-     * @param req Asynchronous request to wait on, if provided.
-     */
-    void addABTioInstance(const std::string& description,
-                          AsyncRequest*      req = nullptr) const;
-
-    /**
-     * @brief Adds an SSG group to the target service daemon.
-     * The group is created from the provided JSON configuration.
-     *
-     * @param description JSON description.
-     * @param req Asynchronous request to wait on, if provided.
-     */
-    void addSSGgroup(const std::string& config,
-                     AsyncRequest*      req = nullptr) const;
-
-
     /**
      * @brief Adds an Argobots pool to the Margo instance of
      * the target service.
diff --git a/python/mochi/bedrock/bedrockctl/__main__.py b/python/mochi/bedrock/bedrockctl/__main__.py
index 63fb13e..cdec62e 100644
--- a/python/mochi/bedrock/bedrockctl/__main__.py
+++ b/python/mochi/bedrock/bedrockctl/__main__.py
@@ -18,18 +18,6 @@
 from .provider import app as provider_app
 app.add_typer(provider_app, name="provider", help="Access and modify providers")
 
-from .client import app as client_app
-app.add_typer(client_app, name="client", help="Access and modify clients")
-
-from .abt_io import app as abt_io_app
-app.add_typer(abt_io_app, name="abtio", help="Access and modify ABT-IO instances")
-
-from .mona import app as mona_app
-app.add_typer(mona_app, name="mona", help="Access and modify MoNA instances")
-
-from .ssg import app as ssg_app
-app.add_typer(ssg_app, name="ssg", help="Access and modify SSG groups")
-
 
 @app.command()
 def status():
diff --git a/python/mochi/bedrock/bedrockctl/_util.py b/python/mochi/bedrock/bedrockctl/_util.py
index b3f8365..32b6845 100644
--- a/python/mochi/bedrock/bedrockctl/_util.py
+++ b/python/mochi/bedrock/bedrockctl/_util.py
@@ -7,7 +7,6 @@
 
 class ServiceContext:
 
-    ssg_prefix = "ssg://"
     flock_prefix = "flock://"
 
     def __init__(self, target=None):
@@ -15,14 +14,6 @@ def __init__(self, target=None):
         if self.connection is None:
             print(f"Error: bedrockctl not connected")
             raise typer.Exit(code=-1)
-        # SSG file
-        if self.connection.startswith(ServiceContext.ssg_prefix):
-            group_file = self.connection[len(ServiceContext.ssg_prefix):]
-            if not (os.path.exists(group_file) and os.path.isfile(group_file)):
-                print(f"Error: could not access SSG file {group_file}")
-                raise typer.Exit(code=-1)
-            import pyssg
-            self.protocol = pyssg.get_group_transport_from_file(group_file)
         # Flock file
         elif self.connection.startswith(ServiceContext.flock_prefix):
             group_file = self.connection[len(ServiceContext.flock_prefix):]
@@ -58,12 +49,7 @@ def __init__(self, target=None):
     def __enter__(self):
         self.engine = Engine(self.protocol)
         client = Client(self.engine)
-        if self.connection.startswith(ServiceContext.ssg_prefix):
-            import pyssg
-            pyssg.init()
-            self.service = client.make_service_group_handle_from_ssg(
-                self.connection[len(ServiceContext.ssg_prefix):])
-        elif self.connection.startswith(ServiceContext.flock_prefix):
+        if self.connection.startswith(ServiceContext.flock_prefix):
             self.service = client.make_service_group_handle_from_flock(
                 self.connection[len(ServiceContext.flock_prefix):])
         else:
@@ -73,9 +59,6 @@ def __enter__(self):
     def __exit__(self, type, value, traceback):
         self.service = None
         del self.service
-        if self.connection.startswith(ServiceContext.ssg_prefix):
-            import pyssg
-            pyssg.finalize()
         self.engine.finalize()
 
 
diff --git a/python/mochi/bedrock/bedrockctl/abt_io.py b/python/mochi/bedrock/bedrockctl/abt_io.py
deleted file mode 100644
index 13f1e26..0000000
--- a/python/mochi/bedrock/bedrockctl/abt_io.py
+++ /dev/null
@@ -1,94 +0,0 @@
-import typer
-from typing_extensions import Annotated
-from enum import Enum
-from typing import Optional, List
-from ..spec import PoolSpec
-from ..client import ClientException
-
-
-app = typer.Typer()
-
-
-@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
-def create(
-        ctx: typer.Context,
-        name: Annotated[
-            str, typer.Argument(help="Name of the ABT-IO instance to create")],
-        pool: Annotated[
-            str, typer.Option(
-                "-p", "--pool", help="Pool for the ABT-IO instance to use")] = "__primary__",
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Create a new ABT-IO instance in the target Bedrock process(es).
-    """
-    from ._util import parse_config_from_args
-    config = parse_config_from_args(ctx.args)
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    abt_io = {
-        "name": name,
-        "pool": pool,
-        "config": config
-    }
-    from ._util import ServiceContext
-    with ServiceContext(target) as service:
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                continue
-            try:
-                service[i].add_abtio_instance(abt_io)
-            except ClientException as e:
-                print(f"Error adding ABT-IO instance in {service[i].address}: {str(e)}")
-        del service
-
-
-@app.command()
-def list(target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-         ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-         ):
-    """
-    Lists the ABT-IO instances in each of the target Bedrock process(es).
-    """
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import ServiceContext
-    from rich import print
-    with ServiceContext(target) as service:
-        config = { a: c["abt_io"] for a, c in service.config.items() }
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                del config[service[i].address]
-        print(config)
-        del service
-
-
-@app.command()
-def remove(
-        name: Annotated[
-            str, typer.Argument(help="Name of the ABT-IO instance to remove")],
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Remove an ABT-IO instance from the target Bedrock process(es).
-    """
-    print("This command is not implemented yet")
-    raise typer.Exit(code=-1)
-
-
-if __name__ == "__main__":
-    app()
diff --git a/python/mochi/bedrock/bedrockctl/client.py b/python/mochi/bedrock/bedrockctl/client.py
deleted file mode 100644
index bb5aa72..0000000
--- a/python/mochi/bedrock/bedrockctl/client.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import typer
-from typing_extensions import Annotated
-from enum import Enum
-from typing import Optional, List
-from ..spec import PoolSpec
-from ..client import ClientException
-
-
-app = typer.Typer()
-
-
-@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
-def create(
-        ctx: typer.Context,
-        name: Annotated[
-            str, typer.Argument(help="Name of the client to create")],
-        type: Annotated[
-            str, typer.Option(
-                "-t", "--type", help="Type of client")],
-        dependencies: Annotated[
-            List[str], typer.Option(
-                "-d", "--dependency", help="Dependency in the form \"name:value\"")] = [],
-        tags: Annotated[
-            List[str], typer.Option(
-                "-a", "--tag", help="Tag")] = [],
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Create a new client in the target Bedrock process(es).
-    """
-    from ._util import parse_config_from_args
-    config = parse_config_from_args(ctx.args)
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import parse_dependencies
-    dependencies = parse_dependencies(dependencies)
-    client = {
-        "name": name,
-        "type": type,
-        "config": config,
-        "dependencies": dependencies,
-        "tags": tags
-    }
-    from ._util import ServiceContext
-    with ServiceContext(target) as service:
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                continue
-            try:
-                service[i].add_client(client)
-            except ClientException as e:
-                print(f"Error adding client in {service[i].address}: {str(e)}")
-        del service
-
-
-@app.command()
-def list(target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-         ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-         ):
-    """
-    Lists the clients in each of the target Bedrock process(es).
-    """
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import ServiceContext
-    from rich import print
-    with ServiceContext(target) as service:
-        config = { a: c["clients"] for a, c in service.config.items() }
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                del config[service[i].address]
-        print(config)
-        del service
-
-
-@app.command()
-def remove(
-        name: Annotated[
-            str, typer.Argument(help="Name of the client to remove")],
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Remove a client from the target Bedrock process(es).
-    """
-    print("This command is not implemented yet")
-    raise typer.Exit(code=-1)
-
-
-if __name__ == "__main__":
-    app()
diff --git a/python/mochi/bedrock/bedrockctl/mona.py b/python/mochi/bedrock/bedrockctl/mona.py
deleted file mode 100644
index 9585c0b..0000000
--- a/python/mochi/bedrock/bedrockctl/mona.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import typer
-from typing_extensions import Annotated
-from enum import Enum
-from typing import Optional, List
-from ..spec import PoolSpec
-from ..client import ClientException
-
-
-app = typer.Typer()
-
-
-@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
-def create(
-        ctx: typer.Context,
-        name: Annotated[
-            str, typer.Argument(help="Name of the MoNA instance to create")],
-        pool: Annotated[
-            str, typer.Option(
-                "-p", "--pool", help="Pool for the MoNA instance to use")] = "__primary__",
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Create a new MoNA instance in the target Bedrock process(es).
-    """
-    print("This command is not implemented yet")
-    raise typer.Exit(code=-1)
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import parse_config_from_args
-    config = parse_config_from_args(ctx.args)
-    mona = {
-        "name": name,
-        "pool": pool,
-        "config": config
-    }
-    from ._util import ServiceContext
-    with ServiceContext(target) as service:
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                continue
-            try:
-                service[i].add_mona_instance(mona)
-            except ClientException as e:
-                print(f"Error adding MoNA instance in {service[i].address}: {str(e)}")
-        del service
-
-
-@app.command()
-def list(target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-         ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-         ):
-    """
-    Lists the MoNA instances in each of the target Bedrock process(es).
-    """
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import ServiceContext
-    from rich import print
-    with ServiceContext(target) as service:
-        config = { a: c["mona"] for a, c in service.config.items() }
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                del config[service[i].address]
-        print(config)
-        del service
-
-
-@app.command()
-def remove(
-        name: Annotated[
-            str, typer.Argument(help="Name of the MoNA instance to remove")],
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Remove an MoNA instance from the target Bedrock process(es).
-    """
-    print("This command is not implemented yet")
-    raise typer.Exit(code=-1)
-
-
-if __name__ == "__main__":
-    app()
diff --git a/python/mochi/bedrock/bedrockctl/ssg.py b/python/mochi/bedrock/bedrockctl/ssg.py
deleted file mode 100644
index 7dd2af8..0000000
--- a/python/mochi/bedrock/bedrockctl/ssg.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import typer
-from typing_extensions import Annotated
-from enum import Enum
-from typing import Optional, List
-from ..spec import PoolSpec
-from ..client import ClientException
-
-
-app = typer.Typer()
-
-
-@app.command()
-def create(
-        name: Annotated[
-            str, typer.Argument(help="Name of the SSG group to create")],
-        file: Annotated[
-            str, typer.Option(
-                "-f", "--file", help="Group file")],
-        pool: Annotated[
-            str, typer.Option(
-                "-p", "--pool", help="Pool for the SSG group to use")] = "__primary__",
-        disable_swim: Annotated[
-            bool, typer.Option("--disable-swim/--enable-swim",
-                               help="Disable SWIM protocol")] = False,
-        swim_period_length_ms: Annotated[
-            int, typer.Option(help="SWIM period length in milliseconds")] = 0,
-        swim_suspect_timeout_periods: Annotated[
-            int, typer.Option(help="SWIM number of suspect timeout periods")] = -1,
-        swim_subgroup_member_count: Annotated[
-            int, typer.Option(help="SWIM subgroup member count")] = -1,
-        credential: Annotated[int, typer.Option(help="Credential")] = -1,
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Create a new SSG group in the target Bedrock process(es).
-    """
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    ssg_group = {
-        "name": name,
-        "pool": pool,
-        "group_file": file,
-        "credential": credential,
-        "bootstrap": "init",
-        "swim": {
-            "disabled": disable_swim,
-            "period_length_ms": swim_period_length_ms,
-            "suspect_timeout_periods": swim_suspect_timeout_periods,
-            "subgroup_member_count": swim_subgroup_member_count
-        }
-    }
-    from ._util import ServiceContext
-    with ServiceContext(target) as service:
-        # TODO: for now we need the first rank to "init" the group
-        # then the next processes need to "join" it. It would be
-        # better to be able to add a list of addresses as agument.
-        for i in range(0, len(service)):
-            if not rank_is_in(i, ranks):
-                continue
-            try:
-                service[i].add_ssg_group(ssg_group)
-                ssg_group["bootstrap"] = "join"
-            except ClientException as e:
-                print(f"Error adding SSG group in {service[i].address}: {str(e)}")
-                if i == 0:
-                    raise typer.Exit(code=-1)
-        del service
-
-
-@app.command()
-def list(target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-         ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-         ):
-    """
-    Lists the SSG groups in each of the target Bedrock process(es).
-    """
-    from ._util import parse_target_ranks, rank_is_in
-    ranks = parse_target_ranks(ranks)
-    from ._util import ServiceContext
-    from rich import print
-    with ServiceContext(target) as service:
-        config = { a: c["ssg"] for a, c in service.config.items() }
-        for i in range(len(service)):
-            if not rank_is_in(i, ranks):
-                del config[service[i].address]
-        print(config)
-        del service
-
-
-@app.command()
-def remove(
-        name: Annotated[
-            str, typer.Argument(help="Name of the SSG group to remove")],
-        target: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Target addresses or group file")] = None,
-        ranks: Annotated[
-            Optional[str], typer.Option(hidden=True,
-                help="Comma-separated list of ranks")] = None
-        ):
-    """
-    Remove an SSG group from the target Bedrock process(es).
-    """
-    print("This command is not implemented yet")
-    raise typer.Exit(code=-1)
-
-
-if __name__ == "__main__":
-    app()
diff --git a/python/mochi/bedrock/client.py b/python/mochi/bedrock/client.py
index a557136..8bf9345 100644
--- a/python/mochi/bedrock/client.py
+++ b/python/mochi/bedrock/client.py
@@ -16,7 +16,7 @@
 import pymargo.core
 import pymargo
 import json
-from .spec import ProcSpec, XstreamSpec, PoolSpec, AbtIOSpec, SSGSpec, ProviderSpec, ClientSpec
+from .spec import ProcSpec, XstreamSpec, PoolSpec, ProviderSpec
 
 
 ClientException = pybedrock_client.Exception
@@ -51,8 +51,8 @@ def spec(self):
     def query(self, script: str):
         return json.loads(self._internal.query_config(script))
 
-    def load_module(self, name: str, path: str):
-        self._internal.load_module(name, path)
+    def load_module(self, path: str):
+        self._internal.load_module(path)
 
     def _ensure_config_str(self, config):
         if isinstance(config, str):
@@ -76,26 +76,10 @@ def add_xstream(self, config: str|dict|XstreamSpec):
     def remove_xstream(self, name: str):
         self._internal.remove_xstream(name)
 
-    def add_ssg_group(self, description: str|dict|SSGSpec):
-        description = self._ensure_config_str(description)
-        self._internal.add_ssg_group(description)
-
-    def add_abtio_instance(self, description: str|dict|AbtIOSpec):
-        description = self._ensure_config_str(description)
-        self._internal.add_abtio_instance(description)
-
-    def add_client(self, description: str|dict|ClientSpec):
-        description = self._ensure_config_str(description)
-        self._internal.add_client(description)
-
     def add_provider(self, description: str|dict|ProviderSpec):
         description = self._ensure_config_str(description)
         return self._internal.add_provider(description)
 
-    def change_provider_pool(self, provider_name: str, pool_name: str):
-        self._internal.change_provider_pool(
-            provider_name, pool_name)
-
 
 class ServiceGroupHandle:
 
@@ -174,16 +158,6 @@ def make_service_group_handle(self, addresses: list[str], provider_id: int = 0):
             self._internal.make_service_group_handle(addresses, provider_id),
             self)
 
-    def make_service_group_handle_from_ssg(self, group: str|int, provider_id: int = 0):
-        if isinstance(group, int):
-            return ServiceGroupHandle(
-                self._internal.make_service_group_handle_from_ssg_group(group, provider_id),
-                self)
-        else:
-            return ServiceGroupHandle(
-                self._internal.make_service_group_handle_from_ssg_file(group, provider_id),
-                self)
-
     def make_service_group_handle_from_flock(self, groupfile: str, provider_id: int = 0):
         return ServiceGroupHandle(
                 self._internal.make_service_group_handle_from_flock_file(groupfile, provider_id),
diff --git a/python/mochi/bedrock/config_space.py b/python/mochi/bedrock/config_space.py
index cb21006..5a651da 100644
--- a/python/mochi/bedrock/config_space.py
+++ b/python/mochi/bedrock/config_space.py
@@ -17,7 +17,7 @@
 from ConfigSpace.hyperparameters import Hyperparameter
 from ConfigSpace.conditions import Condition, Conjunction, ConditionLike
 from ConfigSpace.forbidden import ForbiddenLike
-from typing import Sequence, Any
+from typing import Sequence, Any, Mapping, Hashable
 
 
 # Configuration
@@ -33,6 +33,7 @@
 Constant = ConfigSpace.Constant
 UnParametrizedHyperparameter = ConfigSpace.UnParametrizedHyperparameter
 OrdinalHyperparameter = ConfigSpace.OrdinalHyperparameter
+IntegerHyperparameter = ConfigSpace.hyperparameters.IntegerHyperparameter
 # Conditions
 AndConjunction = ConfigSpace.AndConjunction
 OrConjunction = ConfigSpace.OrConjunction
@@ -68,9 +69,13 @@ def __init__(self, name: str|None = None,
         self._conditions = {} # associates the name of the condition's child to the tuple
                               # (condition_type, parent_name, value)
 
-    def add(self, arg: (Hyperparameter|ConditionLike|ForbiddenLike)):
+    def add(self, arg: (Hyperparameter|ConditionLike|ForbiddenLike|list)):
         if self._frozen:
             raise PermissionError("ConfigurationSpace is already frozen")
+        if isinstance(arg, list):
+            for x in arg:
+                self.add(x)
+            return
         if isinstance(arg, Condition):
             if arg.child.name not in self._conditions:
                 self._conditions[arg.child.name] = []
@@ -148,6 +153,34 @@ def convert_condition_tuples(child_name, conditions: list[tuple[type,str,Any]]):
         return self._inner
 
 
+class PrefixedConfigSpaceWrapper:
+
+    def __init__(self, configuration_space: ConfigurationSpace, prefix: str):
+        self.prefix = prefix
+        self.cs = configuration_space
+
+    def add(self, arg):
+        if isinstance(arg, Hyperparameter):
+            arg.name = self.prefix + arg.name
+        if isinstance(arg, list):
+            for a in arg:
+                self.add(a)
+        else:
+            self.cs.add(arg)
+
+    def __getattr__(self, key):
+        return getattr(self.cs, key)
+
+    def __iter__(self):
+        return self.cs.__iter__()
+
+    def __getitem__(self, name):
+        return self.cs[name]
+
+    def __contains__(self, name):
+        return name in self.cs
+
+
 def CategoricalOrConst(name: str, items: Sequence[Any]|Any, *,
                        default: Any|None = None, weights: Sequence[float]|None = None,
                        ordered: bool = False, meta: dict|None = None):
@@ -209,3 +242,32 @@ def FloatOrConst(name: str, bounds: float|tuple[float, float], *,
     else:
         return Float(name=name, bounds=bounds, distribution=distribution,
                      default=default, log=log, meta=meta)
+
+
+def CategoricalChoice(name: str,
+                      num_options: IntegerHyperparameter|int,
+                      weights: Sequence[float] | None = None,
+                      meta: Mapping[Hashable, Any] | None = None) -> list[Any]:
+    """
+    This function is a helper to build a Categorical hyperparameter with the specified
+    name, whose value must be taken between 0 and num_options-1. num_options can be either
+    and integer or an IntegerHyperparameter. In the later case, this function will also
+    construct the required Forbidden clauses to constrain the value of the choice.
+
+    The result of this function is a list of Hyperparameter and Forbidden clauses, which
+    should then be passed to a ConfigurationSpace's add function.
+    """
+
+    max_options = num_options if isinstance(num_options, int) else int(num_options.upper)
+    min_options = num_options if isinstance(num_options, int) else int(num_options.lower)
+    choice = CategoricalOrConst(name, list(range(max_options)),
+                                default=0, meta=meta, weights=weights)
+    conditions = []
+    if max_options != min_options:
+        for j in range(min_options, max_options):
+            conditions.append(
+                ForbiddenAndConjunction(
+                    ForbiddenEqualsClause(num_options, j),
+                    ForbiddenInClause(choice, list(range(j, max_options)))))
+    return [choice, *conditions]
+
diff --git a/python/mochi/bedrock/server.py b/python/mochi/bedrock/server.py
index 27443a5..599d878 100644
--- a/python/mochi/bedrock/server.py
+++ b/python/mochi/bedrock/server.py
@@ -16,7 +16,7 @@
 import pymargo.core
 import pymargo
 from typing import Mapping, List
-from .spec import ProcSpec, MargoSpec, PoolSpec, XstreamSpec, SSGSpec, AbtIOSpec, ProviderSpec, ClientSpec
+from .spec import ProcSpec, MargoSpec, PoolSpec, XstreamSpec, ProviderSpec
 import json
 
 
@@ -41,16 +41,9 @@ def name(self):
     def type(self):
         return self._internal.type
 
-    @property
-    def handle(self):
-        return self._internal.handle
-
 
 Pool = NamedDependency
 Xstream = NamedDependency
-SSGGroup = NamedDependency
-AbtIOInstance = NamedDependency
-Client = NamedDependency
 
 
 class ProviderDependency(NamedDependency):
@@ -164,137 +157,6 @@ def default_handler_pool(self):
         return Pool(self._internal.default_handler_pool)
 
 
-class SSGManager:
-
-    def __init__(self, internal: pybedrock_server.SSGManager, server: 'Server'):
-        self._internal = internal
-        self._server = server
-
-    @property
-    def config(self):
-        return json.loads(self._internal.config)
-
-    @property
-    def spec(self) -> list[SSGSpec]:
-        abt_spec = self._server.margo.spec.argobots
-        return [SSGSpec.from_dict(group, abt_spec) for group in self.config]
-
-    def resolve(self, location: str) -> pymargo.core.Address:
-        mid = self._server.margo.mid
-        return pymargo.core.Address(
-                mid=mid, hg_addr=self._internal.resolve_address(location),
-                need_del=False).copy()
-
-    def __len__(self) -> int:
-        return self._internal.num_groups
-
-    def __getitem__(self, key: int|str) -> SSGGroup:
-        return SSGGroup(self._internal.get_group(key))
-
-    def __contains__(self, key: str) -> bool:
-        try:
-            self.__getitem__(key)
-            return True
-        except BedrockException:
-            return False
-
-    def create(self, name: str, pool: str|int|Pool = "__primary__",
-               config: str|dict|SSGSpec = "{}",
-               bootstrap: str = "init", group_file: str = "",
-               credential: int = -1) -> SSGGroup:
-        if not isinstance(pool, Pool):
-            pool = self._server.margo.pools[pool]
-        pool = pool._internal
-        if isinstance(config, str):
-            config = json.loads(config)
-        return SSGGroup(self._internal.add_group(name, config, pool, bootstrap, group_file, credential))
-
-
-class AbtIOManager:
-
-    def __init__(self, internal: pybedrock_server.ABTioManager, server: 'Server'):
-        self._internal = internal
-        self._server = server
-
-    @property
-    def config(self):
-        return json.loads(self._internal.config)
-
-    @property
-    def spec(self) -> list[AbtIOSpec]:
-        abt_spec = self._server.spec.margo.argobots
-        return [AbtIOSpec.from_dict(instance, abt_spec=abt_spec) for instance in self.config]
-
-    def __len__(self) -> int:
-        return self._internal.num_abtio_instances
-
-    def __getitem__(self, key: int|str) -> AbtIOInstance:
-        return AbtIOInstance(self._internal.get_abtio_instance(key))
-
-    def __contains__(self, key: str) -> bool:
-        try:
-            self.__getitem__(key)
-            return True
-        except BedrockException:
-            return False
-
-    def create(self, name: str, pool: str|int|Pool, config: str|dict = {}) -> AbtIOInstance:
-        if isinstance(config, str):
-            config = json.loads(config)
-        if not isinstance(pool, Pool):
-            pool = self._server.margo.pools[pool]
-        pool = pool._internal
-        return AbtIOInstance(self._internal.add_abtio_instance(name, pool, config))
-
-
-class ClientManager:
-
-    def __init__(self, internal: pybedrock_server.ClientManager, server: 'Server'):
-        self._internal = internal
-        self._server = server
-
-    @property
-    def config(self) -> dict:
-        return self._internal.config
-
-    @property
-    def spec(self) -> list[ClientSpec]:
-        return [ClientSpec.from_dict(client) for client in self.config]
-
-    def __len__(self):
-        return self._internal.num_clients
-
-    def __getitem__(self, key: int|str) -> Client:
-        return self._internal.get_client(key)
-
-    def __delitem__(self, key: int|str) -> None:
-        self._internal.remove_client(key)
-
-    def __contains__(self, key: str) -> bool:
-        try:
-            self.__getitem__(key)
-            return True
-        except BedrockException:
-            return False
-
-    def get_or_create_anonymous(self, type: str) -> Client:
-        return Client(self._internal.get_client_or_create(type))
-
-    def create(self, name: str, type: str, config: str|dict = "{}",
-               dependencies: dict[str,str|list[str]] = {},
-               tags: list[str] = []) -> Client:
-        if isinstance(config, str):
-            config = json.loads(config)
-        info = {
-            "name": name,
-            "type": type,
-            "dependencies": dependencies,
-            "tags": tags,
-            "config": config
-        }
-        return Client(self._internal.add_client(info))
-
-
 class ProviderManager:
 
     def __init__(self, internal: pybedrock_server.ProviderManager, server: 'Server'):
@@ -308,7 +170,7 @@ def config(self) -> dict:
     @property
     def spec(self) -> list[ProviderSpec]:
         abt_spec = self._server.margo.spec.argobots
-        return [ProviderSpec.from_dict(provider, abt_spec) for provider in self.config]
+        return [ProviderSpec.from_dict(provider) for provider in self.config]
 
     def __len__(self):
         return self._internal.num_providers
@@ -330,11 +192,8 @@ def lookup(self, locator: str):
         return Provider(self, self._internal.lookup_provider(locator))
 
     def create(self, name: str, type: str, provider_id: int = 65535,
-               pool: str|int|Pool = "__primary__",
                config: str|dict = {}, dependencies: dict[str,str|list[str]] = {},
-               tags: list[str] = []) -> Client:
-        if isinstance(pool, Pool):
-            pool = pool.name
+               tags: list[str] = []) -> Provider:
         if isinstance(config, str):
             config = json.loads(config)
         info = {
@@ -409,18 +268,6 @@ def spec(self) -> ProcSpec:
     def margo(self) -> MargoManager:
         return MargoManager(self._internal.margo_manager, self)
 
-    @property
-    def ssg(self) -> SSGManager:
-        return SSGManager(self._internal.ssg_manager, self)
-
-    @property
-    def abtio(self) -> AbtIOManager:
-        return AbtIOManager(self._internal.abtio_manager, self)
-
-    @property
-    def clients(self) -> ClientManager:
-        return ClientManager(self._internal.client_manager, self)
-
     @property
     def providers(self) -> ProviderManager:
         return ProviderManager(self._internal.provider_manager, self)
diff --git a/python/mochi/bedrock/spec.py b/python/mochi/bedrock/spec.py
index 0697426..b531e41 100644
--- a/python/mochi/bedrock/spec.py
+++ b/python/mochi/bedrock/spec.py
@@ -14,11 +14,16 @@
 
 import json
 import attr
+from abc import ABC, abstractmethod
 from attr import Factory
 from attr.validators import instance_of, in_
 from typing import List, NoReturn, Union, Optional, Sequence, Any, Callable, Mapping
 
 
+CS = 'ConfigurationSpace'
+Config = 'Configuration'
+
+
 class _Configurable:
     """
     For some of the classes bellow, initializing from config simply means extracting
@@ -27,7 +32,7 @@ class _Configurable:
     """
 
     @classmethod
-    def from_config(cls, *, config: 'Configuration', prefix: str = '', **kwargs):
+    def from_config(cls, *, config: Config, prefix: str = '', **kwargs):
         expected_attr = set(attribute.name for attribute in cls.__attrs_attrs__)
         expected_kwargs = { k: v for k, v in kwargs.items() if k in expected_attr}
         for param, value in config.items():
@@ -461,7 +466,7 @@ def space(*,
               output_eager_size: int|tuple[int,int] = 4080,
               na_max_expected_size: int|tuple[int,int] = 0,
               na_max_unexpected_size: int|tuple[int,int] = 0,
-              **kwargs):
+              **kwargs) -> CS:
         """
         Create a ConfigurationSpace object to generate MercurySpecs.
         Each of the argument can be set to either a single value or a range
@@ -556,7 +561,7 @@ def validate(self) -> NoReturn:
     @staticmethod
     def space(*,
               pool_kinds: str|list[str] = ['fifo_wait', 'fifo', 'prio_wait', 'earliest_first'],
-              **kwargs):
+              **kwargs) -> CS:
         """
         Create a ConfigurationSpace to generate PoolSpec objects.
         pool_kinds can be specified as a string or a list of strings to force the pool kind
@@ -656,7 +661,7 @@ def validate(self) -> NoReturn:
     @staticmethod
     def space(*,
               scheduler_types: str|list[str] = ['basic_wait', 'default', 'basic', 'prio', 'randws'],
-              **kwargs):
+              **kwargs) -> CS:
         """
         Create a ConfigurationSpace to generate SchedulerSpec objects.
         """
@@ -664,14 +669,6 @@ def space(*,
         cs = ConfigurationSpace()
         default_scheduler_types = scheduler_types[0] if isinstance(scheduler_types, list) else scheduler_types
         cs.add(CategoricalOrConst('type', scheduler_types, default=default_scheduler_types))
-        """
-        for i in range(0, max_num_pools):
-            if isinstance(pool_association_weights, list):
-                weight = pool_association_weights[i]
-            else:
-                weight = pool_association_weights
-            cs.add(FloatOrConst(f'pool_association_weight[{i}]', weight, default=-1.0))
-        """
         return cs
 
 
@@ -782,7 +779,7 @@ def validate(self) -> NoReturn:
         self.scheduler.validate()
 
     @staticmethod
-    def space(*args, **kwargs):
+    def space(*args, **kwargs) -> CS:
         """
         Create a ConfigurationSpace object from which to generate XstreamSpecs.
         This function essentially forwards its arguments to the underlying
@@ -799,7 +796,7 @@ def space(*args, **kwargs):
     def from_config(*,
                     name: str,
                     pools: list[PoolSpec],
-                    config: 'Configuration',
+                    config: Config,
                     prefix: str = ''):
         """
         Create an XstreamSpec from a Configuration object.
@@ -1010,7 +1007,7 @@ def space(*,
               pool_kinds : list[str] = ['fifo_wait', 'fifo', 'prio_wait', 'earliest_first'],
               scheduler_types: str|list[str]|None = ['basic_wait', 'default', 'basic', 'prio', 'randws'],
               mapping_weight_range: tuple[float,float] = (-1.0, 1.0),
-              **kwargs):
+              **kwargs) -> CS:
         """
         Create the ConfigurationSpace of an ArgobotsSpec.
 
@@ -1075,12 +1072,12 @@ def space(*,
 
     @staticmethod
     def from_config(*,
-                    config: 'Configuration',
+                    config: Config,
                     prefix: str = '',
                     pool_name_format: str = '__pool_{}__',
                     xstream_name_format: str = '__xstream_{}__',
                     use_progress_thread: bool = False,
-                    **kwargs):
+                    **kwargs) -> 'ArgobotsSpec':
         """
         Create an ArgobotsSpec from a Configuration object.
         pool_name_format and xstream_name_format are used to format the names
@@ -1298,7 +1295,7 @@ def space(*,
               progress_timeout_ub_msec: int|tuple[int,int] = 100,
               use_progress_thread: bool|tuple[bool,bool] = [True, False],
               rpc_pool: int|tuple[int,int]|None = None,
-              **kwargs):
+              **kwargs) -> CS:
         """
         Create the ConfigurationSpace for a MargoSpec.
 
@@ -1316,7 +1313,8 @@ def space(*,
                 IntegerOrConst,
                 ForbiddenAndConjunction,
                 ForbiddenInClause,
-                ForbiddenEqualsClause)
+                ForbiddenEqualsClause,
+                CategoricalChoice)
         cs = ConfigurationSpace()
         cs.add(CategoricalOrConst('use_progress_thread', use_progress_thread))
         cs.add(IntegerOrConst('handle_cache_size', handle_cache_size))
@@ -1330,20 +1328,11 @@ def space(*,
             prefix='mercury', delimiter='.',
             configuration_space=mercury_cs)
         hp_num_pools = cs['argobots.num_pools']
-        # Note: rpc_pool and progress_pool are categorical because AI tools should not
-        # make the assumption that adding/removing 1 to the value will lead to a smaller
-        # change than adding/removing a larger value.
-        hp_rpc_pool = CategoricalOrConst('rpc_pool', list(range(hp_num_pools.upper)), default=0)
-        cs.add(hp_rpc_pool)
-        # add conditions on the possible values of rpc_pool and progress_pool
-        for i in range(hp_num_pools.lower, hp_num_pools.upper):
-            cs.add(ForbiddenAndConjunction(
-                ForbiddenInClause(hp_rpc_pool, list(range(i, hp_num_pools.upper))),
-                ForbiddenEqualsClause(hp_num_pools, i)))
+        cs.add(CategoricalChoice('rpc_pool', num_options=hp_num_pools))
         return cs
 
     @staticmethod
-    def from_config(*, config: 'Configuration', prefix: str = '', **kwargs):
+    def from_config(*, config: Config, prefix: str = '', **kwargs) -> 'MargoSpec':
         """
         Create a MargoSpec from a Configuration object.
 
@@ -1381,305 +1370,26 @@ def from_config(*, config: 'Configuration', prefix: str = '', **kwargs):
             **extra)
 
 
-@attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
-class AbtIOSpec:
-    """ABT-IO instance specification.
-
-    :param name: Name of the ABT-IO instance
-    :type name: str
-
-    :param pool: Pool associated with the instance
-    :type pool: PoolSpec
-
-    :param config: Configuration
-    :type config: dict
-    """
-
-    name: str = attr.ib(
-        validator=[instance_of(str), _validate_object_name],
-        on_setattr=attr.setters.frozen)
-    pool: PoolSpec = attr.ib(validator=instance_of(PoolSpec))
-    config: dict = attr.ib(validator=instance_of(dict),
-                           factory=dict)
-
-    def to_dict(self) -> dict:
-        """Convert the AbtIOSpec into a dictionary.
-        """
-        return {'name': self.name,
-                'pool': self.pool.name,
-                'config': self.config}
-
-    @staticmethod
-    def from_dict(data: dict, abt_spec: ArgobotsSpec) -> 'AbtIOSpec':
-        """Construct an AbtIOSpec from a dictionary. Since the dictionary
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param data: Dictionary
-        :type data: dict
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
-        """
-        name = data['name']
-        config = data['config']
-        pool = abt_spec.pools[data['pool']]
-        abtio = AbtIOSpec(name=name, pool=pool, config=config)
-        return abtio
-
-    def to_json(self, *args, **kwargs) -> str:
-        """Convert the AbtIOSpec into a JSON string.
-        """
-        return json.dumps(self.to_dict(), *args, **kwargs)
-
-    @staticmethod
-    def from_json(json_string: str,  abt_spec: ArgobotsSpec) -> 'AbtIOSpec':
-        """Construct an AbtIOSpec from a JSON string. Since the JSON string
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param json_string: JSON string
-        :type json_string: str
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
-        """
-        return AbtIOSpec.from_dict(json.loads(json_string), abt_spec)
-
-
-@attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
-class MonaSpec:
-    """MoNA instance specification.
-
-    :param name: Name of the MoNA instance
-    :type name: str
-
-    :param pool: Pool associated with the instance
-    :type pool: PoolSpec
-
-    :param config: Configuration
-    :type config: dict
-    """
-
-    name: str = attr.ib(
-        validator=[instance_of(str), _validate_object_name],
-        on_setattr=attr.setters.frozen)
-    pool: PoolSpec = attr.ib(validator=instance_of(PoolSpec))
-    config: dict = attr.ib(validator=instance_of(dict),
-                           factory=dict)
-
-    def to_dict(self) -> dict:
-        """Convert the MonaSpec into a dictionary.
-        """
-        return {'name': self.name,
-                'pool': self.pool.name,
-                'config': self.config}
-
-    @staticmethod
-    def from_dict(data: dict, abt_spec: ArgobotsSpec) -> 'MonaSpec':
-        """Construct a MonaSpec from a dictionary. Since the dictionary
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param data: Dictionary
-        :type data: dict
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
-        """
-        name = data['name']
-        if 'config' in data:
-            config = data['config']
-        else:
-            config = {}
-        pool = abt_spec.pools[data['pool']]
-        mona = MonaSpec(name=name, pool=pool, config=config)
-        return mona
-
-    def to_json(self, *args, **kwargs) -> str:
-        """Convert the MonaSpec into a JSON string.
-        """
-        return json.dumps(self.to_dict(), *args, **kwargs)
-
-    @staticmethod
-    def from_json(json_string: str,  abt_spec: ArgobotsSpec) -> 'MonaSpec':
-        """Construct an MonaSpec from a JSON string. Since the JSON string
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param json_string: JSON string
-        :type json_string: str
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
-        """
-        return MonaSpec.from_dict(json.loads(json_string), abt_spec)
-
-
-@attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
-class SwimSpec:
-    """Swim specification for SSG.
-
-    :param period_length_ms: Period length in milliseconds
-    :type period_length_ms: int
-
-    :param suspect_timeout_periods: Number of suspect timeout periods
-    :type suspect_timeout_periods: int
-
-    :param subgroup_member_count: Subgroup member count
-    :type subgroup_member_count: int
-
-    :param disabled: Disable Swim
-    :type disabled: bool
-    """
-
-    period_length_ms: int = attr.ib(
-        validator=instance_of(int),
-        default=0)
-    suspect_timeout_periods: int = attr.ib(
-        validator=instance_of(int),
-        default=-1)
-    subgroup_member_count: int = attr.ib(
-        validator=instance_of(int),
-        default=-1)
-    disabled: bool = attr.ib(
-        validator=instance_of(bool),
-        default=False)
-
-    def to_dict(self) -> dict:
-        """Convert the SwimSpec into a dictionary.
-        """
-        return attr.asdict(self)
-
-    @staticmethod
-    def from_dict(data: dict) -> 'SwimSpec':
-        """Construct a SwimSpec from a dictionary.
-        """
-        return SwimSpec(**data)
-
-    def to_json(self, *args, **kwargs) -> str:
-        """Convert the SwimSpec into a JSON string.
-        """
-        return json.dumps(self.to_dict(), *args, **kwargs)
-
-    @staticmethod
-    def from_json(json_string: str) -> 'SwimSpec':
-        """Construct a SwimSpec from a JSON string.
-        """
-        data = json.loads(json_string)
-        return SwimSpec.from_dict(data)
-
-    def validate(self) -> NoReturn:
-        """Validate the state of the MercurySpec, raising an exception
-        if the MercurySpec is not valid.
-        """
-        attr.validate(self)
-
-
-def _swim_from_args(arg) -> SwimSpec:
-    """Construct a SwimSpec from a single argument. If the argument
-    if a dict, its content if forwarded to the SwimSpec constructor.
-    """
-    if isinstance(arg, SwimSpec):
-        return arg
-    elif isinstance(arg, dict):
-        return MargoSpec(**arg)
-    elif arg is None:
-        return SwimSpec(disabled=True)
-    else:
-        raise TypeError(f'cannot convert type {type(arg)} into a SwimSpec')
-
-
-@attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
-class SSGSpec:
-    """SSG group specification.
-
-    :param name: Name of the SSG group
-    :type name: str
-
-    :param pool: Pool associated with the group
-    :type pool: PoolSpec
-
-    :param credential: Credentials
-    :type credential: long
-
-    :param bootstrap: Bootstrap method
-    :type bootstrap: str
-
-    :param group_file: Group file
-    :type group_file: str
-
-    :param swim: Swim parameters
-    :type swim: SwimSpec
-    """
-
-    name: str = attr.ib(
-        validator=[instance_of(str), _validate_object_name],
-        on_setattr=attr.setters.frozen)
-    pool: PoolSpec = attr.ib(
-        validator=instance_of(PoolSpec))
-    credential: int = attr.ib(
-        validator=instance_of(int),
-        default=-1)
-    bootstrap: str = attr.ib(
-        validator=in_(['init', 'join', 'mpi', 'pmix', 'init|join', 'mpi|join', 'pmix|join']))
-    group_file: str = attr.ib(
-        validator=instance_of(str),
-        default='')
-    swim: Optional[SwimSpec] = attr.ib(
-        validator=instance_of(SwimSpec),
-        converter=_swim_from_args,
-        default=None)
+class ProviderConfigSpaceBuilder(ABC):
 
-    def to_dict(self) -> dict:
-        """Convert the SSGSpec into a dictionary.
+    @abstractmethod
+    def set_provider_hyperparameters(self, configuration_space: CS) -> None:
         """
-        result = {'name': self.name,
-                  'pool': self.pool.name,
-                  'credential': self.credential,
-                  'bootstrap': self.bootstrap,
-                  'group_file': self.group_file}
-        if self.swim is not None:
-            result['swim'] = self.swim.to_dict()
-        return result
-
-    @staticmethod
-    def from_dict(data: dict, abt_spec: ArgobotsSpec) -> 'SSGSpec':
-        """Construct an SSGSpec from a dictionary. Since the dictionary
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param data: Dictionary
-        :type data: dict
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
+        This method can add hyperparameters, conditions, and forbidden clauses
+        to the provided configuration_space. These hyperparameters will internally
+        be added to the configuration space with a prefix.
         """
-        args = data.copy()
-        args['pool'] = abt_spec.pools[data['pool']]
-        if 'swim' in args:
-            args['swim'] = SwimSpec.from_dict(args['swim'])
-        ssg = SSGSpec(**args)
-        return ssg
+        pass
 
-    def to_json(self, *args, **kwargs) -> str:
-        """Convert the SSGSpec into a JSON string.
+    @abstractmethod
+    def resolve_to_provider_spec(
+            self, name: str, provider_id: int,
+            config: Config, prefix: str) -> 'ProviderSpec':
         """
-        return json.dumps(self.to_dict(), *args, **kwargs)
-
-    @staticmethod
-    def from_json(json_string: str,  abt_spec: ArgobotsSpec) -> 'SSGSpec':
-        """Construct an SSGSpec from a JSON string. Since the JSON string
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param json_string: JSON string
-        :type json_string: str
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
+        This method should convert a Configuration object into a ProviderSpec,
+        by extracting the configuration and dependencies from the sampled parameters.
         """
-        return SSGSpec.from_dict(json.loads(json_string), abt_spec)
+        pass
 
 
 @attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
@@ -1692,9 +1402,6 @@ class ProviderSpec:
     :param type: Type of provider
     :type type: str
 
-    :param pool: Pool associated with the group
-    :type pool: PoolSpec
-
     :param provider_id: Provider id
     :type provider_id: int
 
@@ -1714,8 +1421,6 @@ class ProviderSpec:
     type: str = attr.ib(
         validator=instance_of(str),
         on_setattr=attr.setters.frozen)
-    pool: PoolSpec = attr.ib(
-        validator=instance_of(PoolSpec))
     provider_id: int = attr.ib(
         default=0,
         validator=instance_of(int))
@@ -1734,27 +1439,19 @@ def to_dict(self) -> dict:
         """
         return {'name': self.name,
                 'type': self.type,
-                'pool': self.pool.name,
                 'provider_id': self.provider_id,
                 'dependencies': self.dependencies,
                 'config': self.config,
                 'tags': self.tags}
 
     @staticmethod
-    def from_dict(data: dict, abt_spec: ArgobotsSpec) -> 'ProviderSpec':
-        """Construct a ProviderSpec from a dictionary. Since the dictionary
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
+    def from_dict(data: dict) -> 'ProviderSpec':
+        """Construct a ProviderSpec from a dictionary.
 
         :param data: Dictionary
         :type data: dict
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
         """
-        args = data.copy()
-        args['pool'] = abt_spec.pools[data['pool']]
-        provider = ProviderSpec(**args)
+        provider = ProviderSpec(**data)
         return provider
 
     def to_json(self, *args, **kwargs) -> str:
@@ -1763,153 +1460,13 @@ def to_json(self, *args, **kwargs) -> str:
         return json.dumps(self.to_dict(), *args, **kwargs)
 
     @staticmethod
-    def from_json(json_string: str,  abt_spec: ArgobotsSpec) -> 'ProviderSpec':
-        """Construct a ProviderSpec from a JSON string. Since the JSON string
-        references the pool by name or index, an ArgobotsSpec is necessary
-        to resolve the reference.
-
-        :param json_string: JSON string
-        :type json_string: str
-
-        :param abt_spec: ArgobotsSpec in which to look for the PoolSpec
-        :type abt_spec: ArgobotsSpec
-        """
-        return ProviderSpec.from_dict(json.loads(json_string), abt_spec)
-
-    @staticmethod
-    def space(*, type: str, max_num_pools: int, tags: list[str] = [],
-              provider_config_space: Optional['ConfigurationSpace'] = None,
-              provider_config_resolver: Callable[['Configuration', str], dict]|None = None,
-              dependency_config_space: Optional['ConfigurationSpace'] = None,
-              dependency_resolver: Callable[['Configuration', str], dict]|None = None):
-        """
-        Create a ConfigurationSpace for a ProviderSpec.
-
-        - type: type of provider.
-        - max_num_pools: maximum number of pools in the underlying process.
-        - tags: list of tags the provider will use.
-        - pool_association_weights: range in which to draw the weight of association between
-          the provider and each pool. The provider will use the pool with the largest weight.
-        - provider_config_space: a ConfigurationSpace for the "config" field of the provider.
-        - provider_config_resolver: a function (or callable) taking a Configuration and a prefix
-          and returning the provider's "config" field (dict) from the Configuration.
-        - dependency_config_space: a ConfigurationSpace for the "dependencies" field of the provider.
-        - dependency_resolver: a function (or callable) taking a Configuration and a prefix
-          and returning the provider's "dependencies" field (dict) from the Configuration.
-        """
-        from .config_space import ConfigurationSpace, FloatOrConst, Categorical, Constant
-        cs = ConfigurationSpace()
-        cs.add(Constant('type', type))
-        cs.add(Constant('tags', tags))
-        if provider_config_space is not None:
-            cs.add_configuration_space(
-                prefix='config', delimiter='.',
-                configuration_space=provider_config_space)
-        cs.add(Constant('config_resolver', provider_config_resolver))
-        if dependency_config_space is not None:
-            cs.add_configuration_space(
-                prefix='dependencies', delimiter='.',
-                configuration_space=dependency_config_space)
-        cs.add(Constant('dependency_resolver', dependency_resolver))
-        cs.add(Categorical('pool', list(range(max_num_pools))))
-        return cs
-
-    @staticmethod
-    def from_config(*, name: str, provider_id: int, pools: list[PoolSpec],
-                    config: 'Configuration', prefix: str = ''):
-        """
-        Create a ProviderSpec from a given Configuration object.
-
-        This function must be also given the name and provider Id to give the provider,
-        as well as the list of pools of the underlying ProcSpec.
-        """
-        from .config_space import Configuration
-        type = config[f'{prefix}type']
-        tags = config[f'{prefix}tags']
-        provider_config_resolver = config[f'{prefix}config_resolver']
-        dependency_resolver = config[f'{prefix}dependency_resolver']
-        pool = pools[int(config[f'{prefix}pool'])]
-        if provider_config_resolver is None:
-            provider_config = {}
-        else:
-            provider_config = provider_config_resolver(config, f'{prefix}config.')
-        if dependency_resolver is None:
-            provider_dependencies = {}
-        else:
-            provider_dependencies = dependency_resolver(config, f'{prefix}dependencies.')
-        return ProviderSpec(
-            name=name, type=type, provider_id=provider_id,
-            pool=pool, config=provider_config, tags=tags,
-            dependencies=provider_dependencies)
-
-
-@attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
-class ClientSpec:
-    """Client specification.
-
-    :param name: Name of the client
-    :type name: str
-
-    :param type: Type of client
-    :type type: str
-
-    :param config: Configuration
-    :type config: dict
-
-    :param dependencies: Dependencies
-    :type dependencies: dict
-
-    :param tags: Tags
-    :type tags: List[str]
-    """
-
-    name: str = attr.ib(
-        validator=[instance_of(str), _validate_object_name],
-        on_setattr=attr.setters.frozen)
-    type: str = attr.ib(
-        validator=instance_of(str),
-        on_setattr=attr.setters.frozen)
-    config: dict = attr.ib(
-        validator=instance_of(dict),
-        factory=dict)
-    dependencies: dict = attr.ib(
-        validator=instance_of(dict),
-        factory=dict)
-    tags: List[str] = attr.ib(
-        validator=instance_of(List),
-        factory=list)
-
-    def to_dict(self) -> dict:
-        """Convert the ClientSpec into a dictionary.
-        """
-        return {'name': self.name,
-                'type': self.type,
-                'dependencies': self.dependencies,
-                'config': self.config,
-                'tags': self.tags}
-
-    @staticmethod
-    def from_dict(data: dict) -> 'ClientSpec':
-        """Construct a ClientSpec from a dictionary.
-
-        :param data: Dictionary
-        :type data: dict
-        """
-        return ClientSpec(**data)
-
-    def to_json(self, *args, **kwargs) -> str:
-        """Convert the ClientSpec into a JSON string.
-        """
-        return json.dumps(self.to_dict(), *args, **kwargs)
-
-    @staticmethod
-    def from_json(json_string: str) -> 'ClientSpec':
-        """Construct a ClientSpec from a JSON string.
+    def from_json(json_string: str) -> 'ProviderSpec':
+        """Construct a ProviderSpec from a JSON string.
 
         :param json_string: JSON string
         :type json_string: str
         """
-        return ClientSpec.from_dict(json.loads(json_string))
+        return ProviderSpec.from_dict(json.loads(json_string))
 
 
 @attr.s(auto_attribs=True, on_setattr=_check_validators, kw_only=True)
@@ -1994,72 +1551,27 @@ class ProcSpec:
     :param margo: Margo specification
     :type margo: MargoSpec
 
-    :param abt_io: List of AbtIOSpec
-    :type abt_io: list
-
-    :param mona: List of MonaSpec
-    :type mona: list
-
-    :param ssg: List of SSGSpec
-    :type ssg: list
-
     :param libraries: Dictionary of libraries
     :type libraries: dict
 
     :param providers: List of ProviderSpec
     :type providers: list
-
-    :param clients: List of ClientSpec
-    :type clients: list
     """
 
     margo: MargoSpec = attr.ib(
         validator=instance_of(MargoSpec),
         converter=_margo_from_args)
-    _abt_io: List[AbtIOSpec] = attr.ib(
+    libraries: list = attr.ib(
         factory=list,
         validator=instance_of(list))
-    _mona: List[MonaSpec] = attr.ib(
-        factory=list,
-        validator=instance_of(list))
-    _ssg: List[SSGSpec] = attr.ib(
-        factory=list,
-        validator=instance_of(list))
-    libraries: dict = attr.ib(
-        factory=dict,
-        validator=instance_of(dict))
     _providers: list = attr.ib(
         factory=list,
         validator=instance_of(list))
-    _clients: list = attr.ib(
-        factory=list,
-        validator=instance_of(list))
     bedrock: BedrockSpec = attr.ib(
         default=Factory(lambda self: BedrockSpec(pool=self.margo.rpc_pool),
                         takes_self=True),
         validator=instance_of(BedrockSpec))
 
-    @property
-    def abt_io(self) -> SpecListDecorator:
-        """Return a decorator to access the internal list of AbtIOSpec
-        and validate changes to this list.
-        """
-        return SpecListDecorator(list=self._abt_io, type=AbtIOSpec)
-
-    @property
-    def mona(self) -> SpecListDecorator:
-        """Return a decorator to access the internal list of MonaSpec
-        and validate changes to this list.
-        """
-        return SpecListDecorator(list=self._mona, type=MonaSpec)
-
-    @property
-    def ssg(self) -> SpecListDecorator:
-        """Return a decorator to access the internal list of SSGSpec
-        and validate changes to this list.
-        """
-        return SpecListDecorator(list=self._ssg, type=SSGSpec)
-
     @property
     def providers(self) -> SpecListDecorator:
         """Return a decorator to access the internal list of ProviderSpec
@@ -2067,23 +1579,12 @@ def providers(self) -> SpecListDecorator:
         """
         return SpecListDecorator(list=self._providers, type=ProviderSpec)
 
-    @property
-    def clients(self) -> SpecListDecorator:
-        """Return a decorator to access the internal list of ClientSpec
-        and validate changes to this list.
-        """
-        return SpecListDecorator(list=self._clients, type=ClientSpec)
-
     def to_dict(self) -> dict:
         """Convert the ProcSpec into a dictionary.
         """
         data = {'margo': self.margo.to_dict(),
-                'abt_io': [a.to_dict() for a in self._abt_io],
-                'ssg': [g.to_dict() for g in self._ssg],
-                'mona': [m.to_dict() for m in self._mona],
                 'libraries': self.libraries,
                 'providers': [p.to_dict() for p in self._providers],
-                'clients': [c.to_dict() for c in self._clients],
                 'bedrock': self.bedrock.to_dict()}
         return data
 
@@ -2092,39 +1593,19 @@ def from_dict(data: dict) -> 'ProcSpec':
         """Construct a ProcSpec from a dictionary.
         """
         margo = MargoSpec.from_dict(data['margo'])
-        abt_io = []
-        mona = []
-        ssg = []
         libraries = dict()
         providers = []
         bedrock = {}
-        clients = []
         if 'libraries' in data:
             libraries = data['libraries']
-        if 'abt_io' in data:
-            for a in data['abt_io']:
-                abt_io.append(AbtIOSpec.from_dict(a, margo.argobots))
-        if 'ssg' in data:
-            for g in data['ssg']:
-                ssg.append(SSGSpec.from_dict(g, margo.argobots))
-        if 'mona' in data:
-            for m in data['mona']:
-                mona.append(MonaSpec.from_dict(m, margo.argobots))
         if 'providers' in data:
             for p in data['providers']:
-                providers.append(ProviderSpec.from_dict(p,  margo.argobots))
-        if 'clients' in data:
-            for c in data['clients']:
-                clients.append(ClientSpec.from_dict(c))
+                providers.append(ProviderSpec.from_dict(p))
         if 'bedrock' in data:
             bedrock = BedrockSpec.from_dict(data['bedrock'], margo.argobots)
         return ProcSpec(margo=margo,
-                        abt_io=abt_io,
-                        ssg=ssg,
-                        mona=mona,
                         libraries=libraries,
                         providers=providers,
-                        clients=clients,
                         bedrock=bedrock)
 
     def to_json(self, *args, **kwargs) -> str:
@@ -2142,21 +1623,6 @@ def validate(self) -> NoReturn:
         """Validate the state of the ProcSpec.
         """
         attr.validate(self)
-        for a in self._abt_io:
-            p = a.pool
-            if p not in self.margo.argobots.pools:
-                raise ValueError(f'Pool "{p.name}" used by ABT-IO instance' +
-                                 ' not found in margo.argobots.pools')
-        for m in self._mona:
-            p = m.pool
-            if p not in self.margo.argobots.pools:
-                raise ValueError(f'Pool "{p.name}" used by MoNA instance' +
-                                 ' not found in margo.argobots.pools')
-        for g in self._ssg:
-            p = g.pool
-            if p not in self.margo.argobots.pools:
-                raise ValueError(f'Pool "{p.name}" used by SSG group' +
-                                 ' not found in margo.argobots.pool')
         for k, v in self.libraries.items():
             if not isinstance(k, str):
                 raise TypeError('Invalid key type found in libraries' +
@@ -2168,14 +1634,10 @@ def validate(self) -> NoReturn:
             if p.type not in self._libraries:
                 raise ValueError('Could not find module library for' +
                                  f'module type {p.name}')
-        for c in self._clients:
-            if c.type not in self._libraries:
-                raise ValueError('Could not find module library for' +
-                                 f'module type {p.name}')
 
     @staticmethod
     def space(*, provider_space_factories: list[dict] = [],
-              **kwargs):
+              **kwargs) -> CS:
         """
         The provider_space_factories argument is a list of dictionaries with the following format.
         ```
@@ -2192,6 +1654,7 @@ def space(*, provider_space_factories: list[dict] = [],
         """
         from .config_space import (
                 ConfigurationSpace,
+                PrefixedConfigSpaceWrapper,
                 GreaterThanCondition,
                 ForbiddenInClause,
                 ForbiddenAndConjunction,
@@ -2213,39 +1676,31 @@ def space(*, provider_space_factories: list[dict] = [],
         # for each family of providers...
         for provider_group in provider_space_factories:
             family = provider_group['family']
-            provider_cs = provider_group['space']
+            builder = provider_group['builder']
             count = provider_group.get('count', 1)
             default_count = count if isinstance(count, int) else count[0]
             # number of providers
             hp_num_providers = IntegerOrConst(f'providers.{family}.num_providers',
                                               count, default=default_count)
             cs.add(hp_num_providers)
-            # add each provider's sub-space
+            cs.add(Constant(f'providers.{family}.builder', builder))
+            # build each provider's sub-space
             for i in range(0, hp_num_providers.upper):
-                space_prefix = f'providers.{family}[{i}]'
-                cs.add_configuration_space(
-                    prefix=space_prefix, delimiter='.',
-                    configuration_space=provider_cs)
-                # get the hyperparameter for the pool used by this provider
-                hp_pool = cs[f'{space_prefix}.pool']
-                # add a constraint on it (cannot have pool > num_pools)
-                # Note: pool is a categorical hyperparameter so we need a
-                # ForbiddenInClause for each subset of values it can take
-                for j in range(min_num_pools, max_num_pools):
-                    cs.add(ForbiddenAndConjunction(
-                        ForbiddenEqualsClause(hp_num_pools, j),
-                        ForbiddenInClause(hp_pool, list(range(j, max_num_pools)))))
+                space_prefix = f'providers.{family}[{i}].'
+                builder.set_provider_hyperparameters(
+                    configuration_space=PrefixedConfigSpaceWrapper(cs, space_prefix))
                 # add conditions on all the hyperparameters of the sub-space,
                 # they exist only if i < num_providers.
                 if i <= hp_num_providers.lower:
                     continue
-                for param in provider_cs:
-                    param_key = f'{space_prefix}.{param}'
-                    cs.add(GreaterThanCondition(cs[param_key], hp_num_providers, i))
+                for param_name in cs:
+                    if not param_name.startswith(space_prefix):
+                        continue
+                    cs.add(GreaterThanCondition(cs[param_name], hp_num_providers, i))
         return cs
 
     @staticmethod
-    def from_config(*, config: 'Configuration', prefix: str = '', **kwargs):
+    def from_config(*, config: Config, prefix: str = '', **kwargs) -> 'ProcSpec':
         """
         Create a ProcSpec from the provided Configuration object.
         Extra parameters (**kwargs) will be propagated to the underlying
@@ -2259,10 +1714,11 @@ def from_config(*, config: 'Configuration', prefix: str = '', **kwargs):
         provider_specs = []
         for family in families:
             num_providers = int(config[f'{prefix}providers.{family}.num_providers'])
+            builder = config[f'{prefix}providers.{family}.builder']
             for i in range(num_providers):
                 provider_specs.append(
-                    ProviderSpec.from_config(
-                        name=f'{family}_{i}', pools=margo_spec.argobots.pools,
+                    builder.resolve_to_provider_spec(
+                        name=f'{family}_{i}',
                         provider_id=current_provider_id,
                         config=config, prefix=f'{prefix}providers.{family}[{i}].'))
                 current_provider_id += 1
@@ -2338,7 +1794,7 @@ def check_provider_dependency(dep: str):
 
     @staticmethod
     def space(*, process_space_factories: list[dict] = [],
-              **kwargs):
+              **kwargs) -> CS:
         """
         The process_space_factories argument is a list of dictionaries with the following format.
         ```
@@ -2384,8 +1840,8 @@ def space(*, process_space_factories: list[dict] = [],
         return cs
 
     @staticmethod
-    def from_config(config: 'Configuration',
-                    prefix: str = '', **kwargs):
+    def from_config(config: Config,
+                    prefix: str = '', **kwargs) -> 'ServiceSpec':
         """
         Create a ServiceSpec from the provided Configuration object.
         Extra parameters (**kwargs) will be propagated to the underlying
@@ -2413,10 +1869,6 @@ def from_config(config: 'Configuration',
 attr.resolve_types(ArgobotsSpec, globals(), locals())
 attr.resolve_types(MargoSpec, globals(), locals())
 attr.resolve_types(ProviderSpec, globals(), locals())
-attr.resolve_types(ClientSpec, globals(), locals())
-attr.resolve_types(AbtIOSpec, globals(), locals())
-attr.resolve_types(SwimSpec, globals(), locals())
-attr.resolve_types(SSGSpec, globals(), locals())
 attr.resolve_types(BedrockSpec, globals(), locals())
 attr.resolve_types(ProcSpec, globals(), locals())
 attr.resolve_types(ServiceSpec, globals(), locals())
diff --git a/python/mochi/bedrock/test_abtio_manager.py b/python/mochi/bedrock/test_abtio_manager.py
deleted file mode 100644
index f456d2b..0000000
--- a/python/mochi/bedrock/test_abtio_manager.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import unittest
-import pymargo.logging
-import mochi.bedrock.server as mbs
-import mochi.bedrock.spec as spec
-
-
-class TestAbtIOManager(unittest.TestCase):
-
-    def setUp(self):
-        config = {
-            "abt_io": [
-                {
-                    "name": "my_abtio",
-                    "pool": "__primary__",
-                    "config": {}
-                }
-            ]
-        }
-        self.server = mbs.Server(address="na+sm", config=config)
-        self.server.margo.engine.logger.set_log_level(pymargo.logging.level.critical)
-
-    def tearDown(self):
-        self.server.finalize()
-        del self.server
-
-    def test_get_abtio_manager(self):
-        abtio = self.server.abtio
-        self.assertIsInstance(abtio, mbs.AbtIOManager)
-        self.assertEqual(len(abtio), 1)
-        self.assertIn("my_abtio", abtio)
-        self.assertNotIn("not_my_abtio", abtio)
-        abtio_A = abtio[0]
-        abtio_B = abtio["my_abtio"]
-        self.assertEqual(abtio_A.name, abtio_B.name)
-        self.assertEqual(abtio_A.type, abtio_B.type)
-        self.assertEqual(abtio_A.handle, abtio_B.handle)
-        with self.assertRaises(mbs.BedrockException):
-            abtio[1]
-        with self.assertRaises(mbs.BedrockException):
-            abtio["bla"]
-
-    def test_abtio_manager_config(self):
-        config = self.server.abtio.config
-        self.assertIsInstance(config, list)
-        self.assertEqual(len(config), 1)
-        abt_io_1 = config[0]
-        self.assertIsInstance(abt_io_1, dict)
-        for key in ["name", "pool", "config"]:
-            self.assertIn(key, abt_io_1)
-
-    def test_abtio_manager_spec(self):
-        spec_list = self.server.abtio.spec
-        self.assertIsInstance(spec_list, list)
-        for s in spec_list:
-            self.assertIsInstance(s, spec.AbtIOSpec)
-
-    def test_add_abtio_instance(self):
-        abtio = self.server.abtio
-        abtio.create(
-            name="my_abtio_2",
-            pool="__primary__")
-        self.assertEqual(len(abtio), 2)
-        abtio_A = abtio[1]
-        abtio_B = abtio["my_abtio_2"]
-        self.assertEqual(abtio_A.name, abtio_B.name)
-        self.assertEqual(abtio_A.type, abtio_B.type)
-        self.assertEqual(abtio_A.handle, abtio_B.handle)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/python/mochi/bedrock/test_client_manager.py b/python/mochi/bedrock/test_client_manager.py
deleted file mode 100644
index b8587e5..0000000
--- a/python/mochi/bedrock/test_client_manager.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import unittest
-import pymargo.logging
-import mochi.bedrock.server as mbs
-import mochi.bedrock.spec as spec
-
-
-class TestClientManager(unittest.TestCase):
-
-    def setUp(self):
-        config = {
-            "libraries": {
-                "module_a": "./libModuleA.so",
-                "module_b": "./libModuleB.so"
-            },
-            "clients": [
-                {
-                    "name": "my_client_A",
-                    "type": "module_a"
-                },
-                {
-                    "name": "my_client_B",
-                    "type": "module_b",
-                    "__if__": "false"
-                }
-            ]
-        }
-        self.server = mbs.Server(address="na+sm", config=config)
-        self.server.margo.engine.logger.set_log_level(pymargo.logging.level.critical)
-
-    def tearDown(self):
-        self.server.finalize()
-        del self.server
-
-    def test_get_client_manager(self):
-        clients = self.server.clients
-        self.assertIsInstance(clients, mbs.ClientManager)
-        self.assertEqual(len(clients), 1)
-        self.assertIn("my_client_A", clients)
-        self.assertNotIn("not_my_client_A", clients)
-        client_A = clients[0]
-        client_B = clients["my_client_A"]
-        self.assertEqual(client_A.name, client_B.name)
-        self.assertEqual(client_A.type, client_B.type)
-        self.assertEqual(client_A.handle, client_B.handle)
-        with self.assertRaises(mbs.BedrockException):
-            c = clients[1]
-        with self.assertRaises(mbs.BedrockException):
-            c = clients["bla"]
-
-    def test_client_manager_config(self):
-        config = self.server.clients.config
-        self.assertIsInstance(config, list)
-        self.assertEqual(len(config), 1)
-        client_1 = config[0]
-        self.assertIsInstance(client_1, dict)
-        for key in ["name", "config", "dependencies", "tags", "type"]:
-            self.assertIn(key, client_1)
-
-    def test_client_manager_spec(self):
-        spec_list = self.server.clients.spec
-        self.assertIsInstance(spec_list, list)
-        for s in spec_list:
-            self.assertIsInstance(s, spec.ClientSpec)
-
-    def test_add_client(self):
-        clients = self.server.clients
-        clients.create(
-            name="my_client_B",
-            type="module_b")
-        self.assertEqual(len(clients), 2)
-        client_A = clients[1]
-        client_B = clients["my_client_B"]
-        self.assertEqual(client_A.name, client_B.name)
-        self.assertEqual(client_A.type, client_B.type)
-        self.assertEqual(client_A.handle, client_B.handle)
-
-    def test_remove_client(self):
-        self.test_add_client()
-        clients = self.server.clients
-        del clients["my_client_B"]
-        self.assertEqual(len(clients), 1)
-        with self.assertRaises(mbs.BedrockException):
-            c = clients[1]
-        with self.assertRaises(mbs.BedrockException):
-            c = clients["my_provider_B"]
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/python/mochi/bedrock/test_config_space.py b/python/mochi/bedrock/test_config_space.py
index 4045593..acf27ec 100644
--- a/python/mochi/bedrock/test_config_space.py
+++ b/python/mochi/bedrock/test_config_space.py
@@ -1,5 +1,27 @@
 import unittest
 from mochi.bedrock.spec import *
+from .config_space import ConfigurationSpace, Integer, CategoricalChoice
+
+class MyDatabaseSpaceBuilder(ProviderConfigSpaceBuilder):
+
+    def set_provider_hyperparameters(self, configuration_space: CS) -> None:
+        configuration_space.add(Integer("x", (0,9)))
+        configuration_space.add(Integer("y", (1,42)))
+        # add a pool dependency
+        num_pools = configuration_space["margo.argobots.num_pools"]
+        configuration_space.add(CategoricalChoice("pool", num_options=num_pools))
+
+    def resolve_to_provider_spec(
+            self, name: str, provider_id: int, config: Config, prefix: str) -> ProviderSpec:
+        cfg = {
+            "x" : int(config[prefix + "x"]),
+            "y" : int(config[prefix + "y"])
+        }
+        dep = {
+            "pool" : int(config[prefix + "pool"])
+        }
+        return ProviderSpec(name=name, type="yokan", provider_id=provider_id,
+                            tags=["tag1", "tag2"], config=cfg, dependencies=dep)
 
 
 class TestConfigSpace(unittest.TestCase):
@@ -69,73 +91,15 @@ def test_proc_config_space(self):
         spec = ProcSpec.from_config(config=config, address='na+sm')
         #print(spec.to_json(indent=4))
 
-    def test_provider_config_space(self):
-        from .config_space import ConfigurationSpace, Integer
-        # pools to select from
-        pools = [PoolSpec(name=f'pool_{i}') for i in range(5)]
-
-        # config space for the provider
-        config_cs = ConfigurationSpace()
-        config_cs.add(Integer("x", (0,9)))
-        config_cs.add(Integer("y", (1,42)))
-
-        # function to resolve the configuration
-        def resolve_provider_config(config: 'Configuration', prefix: str) -> dict:
-            x = config[f'{prefix}x']
-            y = config[f'{prefix}y']
-            return {'x':x, 'y':y}
-
-        # function to resolve the dependencies
-        def resolve_provider_dependencies(config: 'Configuration', prefix: str) -> dict:
-            return {'abc': 'def'}
-
-        space = ProviderSpec.space(
-            type='yokan', max_num_pools=len(pools),
-            tags=['tag1', 'tag2'],
-            provider_config_space=config_cs,
-            provider_config_resolver=resolve_provider_config,
-            dependency_resolver=resolve_provider_dependencies).freeze()
-        #print(space)
-
-        config = space.sample_configuration()
-        #print(config)
-
-        spec = ProviderSpec.from_config(
-            name='my_yokan_provider', provider_id=1,
-            pools=pools, config=config)
-        #print(spec.to_json(indent=4))
-
     def test_proc_with_providers(self):
-        from .config_space import ConfigurationSpace, Integer
-
-        max_num_pools = 3
-
-        provider_config_cs = ConfigurationSpace()
-        provider_config_cs.add(Integer("x", (0,9)))
-        provider_config_cs.add(Integer("y", (1,42)))
-
-        def resolve_provider_config(config: 'Configuration', prefix: str) -> dict:
-            x = config[f'{prefix}x']
-            y = config[f'{prefix}y']
-            return {'x':x, 'y':y}
-
-        def resolve_provider_dependencies(config: 'Configuration', prefix: str) -> dict:
-            return {'abc': 'def'}
-
         provider_space_factories = [
             {
                 "family": "databases",
-                "space": ProviderSpec.space(
-                    type='yokan',
-                    max_num_pools=max_num_pools, tags=['tag1', 'tag2'],
-                    provider_config_space=provider_config_cs,
-                    provider_config_resolver=resolve_provider_config,
-                    dependency_resolver=resolve_provider_dependencies),
+                "builder": MyDatabaseSpaceBuilder(),
                 "count": (1,3)
             }
         ]
-
-        space = ProcSpec.space(num_pools=(1, max_num_pools), num_xstreams=(2, 5),
+        space = ProcSpec.space(num_pools=(1, 3), num_xstreams=(2, 5),
                                provider_space_factories=provider_space_factories).freeze()
         #print(space)
         config = space.sample_configuration()
@@ -169,5 +133,41 @@ def test_service_config_space(self):
         spec = ServiceSpec.from_config(address='na+sm', config=config)
         #print(spec.to_json(indent=4))
 
+    def test_service_config_space_with_providers(self):
+
+        provider_space_factories = [
+            {
+                "family": "databases",
+                "builder": MyDatabaseSpaceBuilder(),
+                "count": (1,3)
+            }
+        ]
+
+        proc_type_a = ProcSpec.space(num_pools=3, num_xstreams=3,
+                                     provider_space_factories=provider_space_factories)
+        proc_type_b = ProcSpec.space(num_pools=2, num_xstreams=2,
+                                     provider_space_factories=provider_space_factories)
+
+        space = ServiceSpec.space(
+            process_space_factories=[
+                {
+                    'family': 'proc_type_a',
+                    'space': proc_type_a,
+                    'count': 2
+                },
+                {
+                    'family': 'proc_type_b',
+                    'space': proc_type_b,
+                    'count': 2
+                }
+            ]).freeze()
+        #print(space)
+
+        config = space.sample_configuration()
+        #print(config)
+
+        spec = ServiceSpec.from_config(address='na+sm', config=config)
+        #print(spec.to_json(indent=4))
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/python/mochi/bedrock/test_dependencies.py b/python/mochi/bedrock/test_dependencies.py
index a2c8a9e..aea7ddf 100644
--- a/python/mochi/bedrock/test_dependencies.py
+++ b/python/mochi/bedrock/test_dependencies.py
@@ -23,11 +23,11 @@ def setUp(self):
                     ]
                 }
             },
-            "libraries": {
-                "module_a": "./libModuleA.so",
-                "module_b": "./libModuleB.so",
-                "module_c": "./libModuleC.so",
-            },
+            "libraries": [
+                "./libModuleA.so",
+                "./libModuleB.so",
+                "./libModuleC.so",
+            ],
             "providers": [
                 {
                     "name": "my_provider_a",
@@ -39,35 +39,6 @@ def setUp(self):
                     "type": "module_b",
                     "provider_id": 2
                 }
-            ],
-            "clients": [
-                {
-                    "name": "my_client_a",
-                    "type": "module_a"
-                },
-                {
-                    "name": "my_client_b",
-                    "type": "module_b"
-                }
-            ],
-            "abt_io": [
-                {
-                    "name": "my_abt_io",
-                    "pool": "__primary__"
-                }
-            ],
-            "ssg": [
-                {
-                    "name": "my_ssg",
-                    "bootstrap": "init",
-                    "swim": { "disabled": True }
-                }
-            ],
-            "mona": [
-                {
-                    "name": "my_mona",
-                    "pool": "__primary__"
-                }
             ]
         }
         self.server = mbs.Server(address="na+sm", config=config)
@@ -77,7 +48,7 @@ def tearDown(self):
         self.server.finalize()
         del self.server
 
-    def make_client_params(self, expected_dependencies: dict={}):
+    def make_provider_params(self, expected_dependencies: dict={}):
         params = {
             "name": "my_provider_C",
             "type": "module_c",
@@ -85,87 +56,41 @@ def make_client_params(self, expected_dependencies: dict={}):
                 "expected_client_dependencies": expected_dependencies
             }
         }
-        return params
-
-    def make_provider_params(self, expected_dependencies: dict={}):
-        params = self.make_client_params({})
         params["provider_id"] = 3
-        params["pool"] = "my_pool"
         params["config"]["expected_provider_dependencies"] = expected_dependencies
         return params
 
-
     def test_no_dependency(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        client_params = self.make_client_params()
-        clients.create(**client_params)
 
         provider_params = self.make_provider_params()
         providers.create(**provider_params)
 
         self.assertEqual(len(providers), 3)
-        self.assertEqual(len(clients), 3)
 
     def test_optional_dependency(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        client_params = self.make_client_params([
-            {"name": "dep1",
-             "type": "module_a",
-             "kind": "provider_handle",
-             "is_array": False,
-             "is_required": False,
-            }])
-        clients.create(**client_params)
 
         provider_params = self.make_provider_params([
             {"name": "dep1",
              "type": "module_a",
-             "kind": "provider_handle",
              "is_array": False,
              "is_required": False,
             }])
         providers.create(**provider_params)
 
         self.assertEqual(len(providers), 3)
-        self.assertEqual(len(clients), 3)
 
     def test_required_dependency(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1",
-             "type": "module_a",
-             "kind": "provider_handle",
-             "is_array": False,
-             "is_required": True,
-            }])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {
-            "dep1": "my_provider_a@local"
-        }
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
             {"name": "dep1",
              "type": "module_a",
-             "kind": "provider_handle",
              "is_array": False,
              "is_required": True,
             }])
@@ -182,24 +107,6 @@ def test_required_dependency(self):
     def test_dependency_on_pool(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "pool", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with a wrong dependency
-        client_params["dependencies"] = {"dep1": "my_pool_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_pool"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
@@ -217,225 +124,74 @@ def test_dependency_on_pool(self):
         providers.create(**provider_params)
         self.assertEqual(len(providers), 3)
 
-    def test_dependency_on_xstream(self):
-        providers = self.server.providers
-        self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "xstream", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_xstream_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_xstream"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
-
-        # Try creating a provider without the required dependency
-        provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "xstream", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the wrong dependency
-        provider_params["dependencies"] = {"dep1": "my_xstream_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": "my_xstream"}
-        providers.create(**provider_params)
-        self.assertEqual(len(providers), 3)
-
-    def test_dependency_on_abt_io(self):
+    def test_dependency_on_pool_by_index(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "abt_io", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client witht the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_abt_io_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_abt_io"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "abt_io", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the wrong dependency
-        provider_params["dependencies"] = {"dep1": "my_abt_io_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": "my_abt_io"}
-        providers.create(**provider_params)
-        self.assertEqual(len(providers), 3)
-
-    def test_dependency_on_ssg(self):
-        providers = self.server.providers
-        self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "ssg", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_ssg_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_ssg"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
-
-        # Try creating a provider without the required dependency
-        provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "ssg", "is_required": True}])
+            {"name": "dep1", "type": "pool", "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
-        # Try creating a provider with the wrong dependency
-        provider_params["dependencies"] = {"dep1": "my_ssg_bad"}
+        # Try creating a provider with a wrong dependency
+        provider_params["dependencies"] = {"dep1": 123}
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
         # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": "my_ssg"}
+        provider_params["dependencies"] = {"dep1": 1}
         providers.create(**provider_params)
         self.assertEqual(len(providers), 3)
 
-    def test_dependency_on_mona(self):
+    def test_dependency_on_xstream(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "mona", "is_required": True}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_mona_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_mona"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "mona", "is_required": True}])
+            {"name": "dep1", "type": "xstream", "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
         # Try creating a provider with the wrong dependency
-        provider_params["dependencies"] = {"dep1": "my_mona_bad"}
+        provider_params["dependencies"] = {"dep1": "my_xstream_bad"}
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
         # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": "my_mona"}
+        provider_params["dependencies"] = {"dep1": "my_xstream"}
         providers.create(**provider_params)
         self.assertEqual(len(providers), 3)
 
-    def test_dependency_on_client(self):
+    def test_dependency_on_xstream_by_index(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "client"}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_client_a_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_client_a"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "client"}])
+            {"name": "dep1", "type": "xstream", "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
         # Try creating a provider with the wrong dependency
-        provider_params["dependencies"] = {"dep1": "my_client_a_bad"}
+        provider_params["dependencies"] = {"dep1": 123}
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
         # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": "my_client_a"}
+        provider_params["dependencies"] = {"dep1": 1}
         providers.create(**provider_params)
         self.assertEqual(len(providers), 3)
 
     def test_dependency_on_provider(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider"}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong dependency
-        client_params["dependencies"] = {"dep1": "my_provider_a_bad"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "my_provider_a"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
             {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider"}])
+             "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
@@ -452,31 +208,10 @@ def test_dependency_on_provider(self):
     def test_dependency_on_provider_with_id(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider"}])
-
-        # Try creating a client with dependency on the wrong provider ID
-        client_params["dependencies"] = {"dep1": "module_a:999"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with dependency on the wrong provider type
-        client_params["dependencies"] = {"dep1": "module_b:2"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": "module_a:1"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
 
         provider_params = self.make_provider_params([
             {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider"}])
+             "is_required": True}])
 
         # Try creating a provider with dependency on the wrong provider ID
         provider_params["dependencies"] = {"dep1": "module_a:999"}
@@ -496,38 +231,14 @@ def test_dependency_on_provider_with_id(self):
     def test_dependency_on_ph_with_id_and_address(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
 
         # Get the address of this process to use instead of "local"
         address = str(self.server.margo.engine.address)
 
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong provider ID
-        client_params["dependencies"] = {"dep1": "module_a:999@{address}"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong module
-        client_params["dependencies"] = {"dep1": "module_b:1@{address}"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": f"module_a:1@{address}"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
-
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
             {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
+             "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
@@ -549,38 +260,14 @@ def test_dependency_on_ph_with_id_and_address(self):
     def test_dependency_on_ph_with_id_and_rank(self):
         providers = self.server.providers
         self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
 
         # Get the rank of the process to use instead of "local"
         rank = 0
 
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong provider ID
-        client_params["dependencies"] = {"dep1": "module_a:999@{rank}"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong module
-        client_params["dependencies"] = {"dep1": "module_b:1@{rank}"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": f"module_a:1@{rank}"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
-
         # Try creating a provider without the required dependency
         provider_params = self.make_provider_params([
             {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
+             "is_required": True}])
         with self.assertRaises(mbs.BedrockException):
             providers.create(**provider_params)
 
@@ -599,59 +286,6 @@ def test_dependency_on_ph_with_id_and_rank(self):
         providers.create(**provider_params)
         self.assertEqual(len(providers), 3)
 
-    def test_dependency_on_ph_with_ssg(self):
-        providers = self.server.providers
-        self.assertEqual(len(providers), 2)
-        clients = self.server.clients
-        self.assertEqual(len(clients), 2)
-
-        # Get the address of this process to use instead of "local"
-        address = "ssg://my_ssg/0"
-
-        # Try creating a client without the required dependency
-        client_params = self.make_client_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong group
-        client_params["dependencies"] = {"dep1": "module_a:1@ssg://wrong_group/0"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the wrong rank
-        client_params["dependencies"] = {"dep1": "module_a:1@ssg://my_ssg/123"}
-        with self.assertRaises(mbs.BedrockException):
-            clients.create(**client_params)
-
-        # Try creating a client with the required dependency
-        client_params["dependencies"] = {"dep1": f"module_a:1@{address}"}
-        clients.create(**client_params)
-        self.assertEqual(len(clients), 3)
-
-        # Try creating a provider without the required dependency
-        provider_params = self.make_provider_params([
-            {"name": "dep1", "type": "module_a",
-             "is_required": True, "kind": "provider_handle"}])
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the wrong group
-        provider_params["dependencies"] = {"dep1": "module_a:1@ssg://wrong_group/0"}
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the wrong rank
-        provider_params["dependencies"] = {"dep1": "module_a:1@ssg://my_ssg/123"}
-        with self.assertRaises(mbs.BedrockException):
-            providers.create(**provider_params)
-
-        # Try creating a provider with the required dependency
-        provider_params["dependencies"] = {"dep1": f"module_a:1@{address}"}
-        providers.create(**provider_params)
-        self.assertEqual(len(providers), 3)
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/python/mochi/bedrock/test_provider_manager.py b/python/mochi/bedrock/test_provider_manager.py
index dc7960a..e14b975 100644
--- a/python/mochi/bedrock/test_provider_manager.py
+++ b/python/mochi/bedrock/test_provider_manager.py
@@ -8,10 +8,10 @@ class TestProviderManager(unittest.TestCase):
 
     def setUp(self):
         config = {
-            "libraries": {
-                "module_a": "./libModuleA.so",
-                "module_b": "./libModuleB.so"
-            },
+            "libraries": [
+                "./libModuleA.so",
+                "./libModuleB.so"
+            ],
             "providers": [
                 {
                     "name": "my_provider_A",
@@ -44,7 +44,6 @@ def test_get_provider_manager(self):
         self.assertEqual(provider_A.name, provider_B.name)
         self.assertEqual(provider_A.type, provider_B.type)
         self.assertEqual(provider_A.provider_id, provider_B.provider_id)
-        self.assertEqual(provider_A.handle, provider_B.handle)
         with self.assertRaises(mbs.BedrockException):
             p = providers[1]
         with self.assertRaises(mbs.BedrockException):
@@ -56,7 +55,7 @@ def test_provider_manager_config(self):
         self.assertEqual(len(config), 1)
         provider_1 = config[0]
         self.assertIsInstance(provider_1, dict)
-        for key in ["name", "pool", "config", "provider_id", "dependencies", "tags", "type"]:
+        for key in ["name", "config", "provider_id", "dependencies", "tags", "type"]:
             self.assertIn(key, provider_1)
 
     def test_provider_manager_spec(self):
@@ -69,7 +68,6 @@ def test_add_provider(self):
         providers = self.server.providers
         providers.create(
             name="my_provider_B",
-            pool="__primary__",
             provider_id=2,
             type="module_b")
         self.assertEqual(len(providers), 2)
@@ -78,7 +76,6 @@ def test_add_provider(self):
         self.assertEqual(provider_A.name, provider_B.name)
         self.assertEqual(provider_A.type, provider_B.type)
         self.assertEqual(provider_A.provider_id, provider_B.provider_id)
-        self.assertEqual(provider_A.handle, provider_B.handle)
 
     def test_remove_provider(self):
         self.test_add_provider()
diff --git a/python/mochi/bedrock/test_server.py b/python/mochi/bedrock/test_server.py
index a3ec79c..0f8fa52 100644
--- a/python/mochi/bedrock/test_server.py
+++ b/python/mochi/bedrock/test_server.py
@@ -36,7 +36,7 @@ def test_server_config(self):
         server = mbs.Server(address="na+sm")
         config = server.config
         self.assertIsInstance(config, dict)
-        for expected_key in ["margo", "abt_io", "ssg", "bedrock", "providers", "clients", "libraries", "mona"]:
+        for expected_key in ["margo", "bedrock", "providers", "libraries"]:
             self.assertIn(expected_key, config)
         server.finalize()
 
diff --git a/python/mochi/bedrock/test_service_group_handle.py b/python/mochi/bedrock/test_service_group_handle.py
index b8f5448..517269b 100644
--- a/python/mochi/bedrock/test_service_group_handle.py
+++ b/python/mochi/bedrock/test_service_group_handle.py
@@ -10,12 +10,22 @@ class TestServiceGroupHandleInit(unittest.TestCase):
 
     def setUp(self):
         self.tempdir = tempfile.TemporaryDirectory()
-        self.groupfile = os.path.join(self.tempdir.name, "group.ssg")
+        self.groupfile = os.path.join(self.tempdir.name, "group.flock")
         config = {
-            "ssg": [{
+            "libraries": [
+                "libflock-bedrock-module.so"
+            ],
+            "providers": [{
                 "name": "my_group",
-                "bootstrap": "init",
-                "group_file": self.groupfile
+                "type": "flock",
+                "provider_id" : 1,
+                "config": {
+                    "bootstrap": "self",
+                    "file": self.groupfile,
+                    "group": {
+                        "type": "static"
+                    }
+                },
             }]
         }
         self.server = mbs.Server(address="na+sm", config=config)
@@ -27,44 +37,44 @@ def tearDown(self):
         self.server.finalize()
         del self.server
 
-    def test_make_service_group_handle_from_file(self):
-        """
-        Note: because the server and client are on the same process,
-        trying to open the group file will lead to an SSG error
-        about the group ID already existing.
-        """
-        # sgh = self.client.make_service_group_handle(self.groupfile)
-        # self.assertIsInstance(sgh, mbc.ServiceGroupHandle)
-
-    def test_make_service_group_handle_from_gid(self):
-        ssg_group = self.server.ssg["my_group"]
-        gid = ssg_group.handle
-        sgh = self.client.make_service_group_handle_from_ssg(gid)
-        self.assertIsInstance(sgh, mbc.ServiceGroupHandle)
-        sgh.refresh() # just to get code coverage
-
     def test_make_service_group_handle_from_address(self):
         address = str(self.server.margo.engine.address)
         sgh = self.client.make_service_group_handle([address])
         self.assertIsInstance(sgh, mbc.ServiceGroupHandle)
 
+    def test_make_service_group_handle_from_file(self):
+        sgh = self.client.make_service_group_handle_from_flock(self.groupfile)
+        self.assertIsInstance(sgh, mbc.ServiceGroupHandle)
+
 
 class TestServiceGroupHandle(unittest.TestCase):
 
     def setUp(self):
         self.tempdir = tempfile.TemporaryDirectory()
-        self.groupfile = os.path.join(self.tempdir.name, "group.ssg")
+        self.groupfile = os.path.join(self.tempdir.name, "group.flock")
         config = {
-            "ssg": [{
+            "libraries": [
+                "libflock-bedrock-module.so"
+            ],
+            "providers": [{
                 "name": "my_group",
-                "bootstrap": "init",
-                "group_file": self.groupfile
+                "type": "flock",
+                "provider_id" : 1,
+                "config": {
+                    "bootstrap": "self",
+                    "file": self.groupfile,
+                    "group": {
+                        "type": "static"
+                    }
+                },
+                "dependencies": {
+                    "pool": "__primary__"
+                }
             }]
         }
         self.server = mbs.Server(address="na+sm", config=config)
         self.client = mbc.Client(self.server.margo.engine)
-        self.sgh = self.client.make_service_group_handle_from_ssg(
-            self.server.ssg["my_group"].handle)
+        self.sgh = self.client.make_service_group_handle_from_flock(self.groupfile)
 
     def tearDown(self):
         del self.sgh
@@ -88,7 +98,7 @@ def test_config(self):
         self.assertEqual(len(config.keys()), 1)
         self_address = str(self.server.margo.engine.address)
         self.assertIn(self_address, config)
-        for k in ["margo", "providers", "clients", "ssg", "abt_io", "bedrock"]:
+        for k in ["margo", "providers", "bedrock"]:
             self.assertIn(k, config[self_address])
 
     def test_spec(self):
diff --git a/python/mochi/bedrock/test_service_handle.py b/python/mochi/bedrock/test_service_handle.py
index 2bcfd40..e5a3ba4 100644
--- a/python/mochi/bedrock/test_service_handle.py
+++ b/python/mochi/bedrock/test_service_handle.py
@@ -52,7 +52,7 @@ def test_client(self):
     def test_config(self):
         config = self.sh.config
         self.assertIsInstance(config, dict)
-        for k in ["margo", "providers", "clients", "ssg", "abt_io", "bedrock"]:
+        for k in ["margo", "providers", "bedrock"]:
             self.assertIn(k, config)
 
     def test_spec(self):
@@ -67,10 +67,10 @@ def test_query(self):
         s = spec.ArgobotsSpec.from_dict(result)
 
     def test_load_module(self):
-        self.sh.load_module("module_a", "./libModuleA.so")
-        self.sh.load_module("module_b", "./libModuleB.so")
+        self.sh.load_module("./libModuleA.so")
+        self.sh.load_module("./libModuleB.so")
         with self.assertRaises(mbc.ClientException):
-            self.sh.load_module("module_x", "./libModuleX.so")
+            self.sh.load_module("./libModuleX.so")
 
     def add_pool(self, config):
         initial_num_pools = len(self.server.margo.pools)
@@ -180,118 +180,11 @@ def test_remove_xstream(self):
         with self.assertRaises(mbc.ClientException):
             self.server.margo.xstreams["my_xstream"]
 
-    def test_add_ssg_group_from_dict(self):
-        group_config = {
-            "name": "my_group",
-            "pool": "__primary__",
-            "bootstrap": "init",
-            "swim": {
-                "disabled": True
-            }
-        }
-        self.sh.add_ssg_group(group_config)
-        group = self.server.ssg["my_group"]
-        self.assertIsInstance(group, mbs.SSGGroup)
-
-    def test_add_ssg_group_from_str(self):
-        group_config = {
-            "name": "my_group",
-            "pool": "__primary__",
-            "bootstrap": "init",
-            "swim": {
-                "disabled": True
-            }
-        }
-        self.sh.add_ssg_group(json.dumps(group_config))
-        group = self.server.ssg["my_group"]
-        self.assertIsInstance(group, mbs.SSGGroup)
-
-    def test_add_ssg_group_from_spec(self):
-        group_config = spec.SSGSpec(
-            name="my_group",
-            pool=spec.PoolSpec(name="__primary__", kind="fifo_wait", access="mpmc"),
-            bootstrap="init",
-            swim=spec.SwimSpec(disabled=True)
-        )
-        self.sh.add_ssg_group(group_config)
-        group = self.server.ssg["my_group"]
-        self.assertIsInstance(group, mbs.SSGGroup)
-
-    def test_add_abtio_instance_from_dict(self):
-        abtio_config = {
-            "name": "my_abtio",
-            "pool": "__primary__",
-            "config": {}
-        }
-        self.sh.add_abtio_instance(abtio_config)
-        abtio = self.server.abtio["my_abtio"]
-        self.assertIsInstance(abtio, mbs.AbtIOInstance)
-
-    def test_add_abtio_instance_from_src(self):
-        abtio_config = {
-            "name": "my_abtio",
-            "pool": "__primary__",
-            "config": {}
-        }
-        self.sh.add_abtio_instance(json.dumps(abtio_config))
-        abtio = self.server.abtio["my_abtio"]
-        self.assertIsInstance(abtio, mbs.AbtIOInstance)
-
-    def test_add_abtio_instance_from_spec(self):
-        abtio_config = spec.AbtIOSpec(
-            name="my_abtio",
-            pool=spec.PoolSpec(name="__primary__", kind="fifo_wait", access="mpmc"),
-            config={}
-        )
-        self.sh.add_abtio_instance(abtio_config)
-        abtio = self.server.abtio["my_abtio"]
-        self.assertIsInstance(abtio, mbs.AbtIOInstance)
-
-    def test_add_client_from_dict(self):
-        self.test_load_module()
-        client_config = {
-            "name": "my_client",
-            "type": "module_a",
-            "config": {},
-            "dependencies": {},
-            "tags": ["my_tag_1", "my_tag_2"]
-        }
-        self.sh.add_client(client_config)
-        proc_spec = self.server.spec
-        client_spec = proc_spec.clients["my_client"]
-
-    def test_add_client_from_src(self):
-        self.test_load_module()
-        client_config = {
-            "name": "my_client",
-            "type": "module_a",
-            "config": {},
-            "dependencies": {},
-            "tags": ["my_tag_1", "my_tag_2"]
-        }
-        self.sh.add_client(json.dumps(client_config))
-        proc_spec = self.server.spec
-        client_spec = proc_spec.clients["my_client"]
-
-    def test_add_client_from_spec(self):
-        self.test_load_module()
-        client_config = spec.ClientSpec(
-            name="my_client",
-            type="module_a",
-            config={},
-            dependencies={},
-            tags=["my_tag_1", "my_tag_2"]
-        )
-        self.sh.add_client(client_config)
-        proc_spec = self.server.spec
-        client_spec = proc_spec.clients["my_client"]
-
     def test_add_provider_from_dict(self):
         self.test_load_module()
         provider_config = {
             "name": "my_provider",
             "type": "module_a",
-            "pool": "__primary__",
             "provider_id": 42,
             "config": {},
             "dependencies": {},
@@ -306,7 +199,6 @@ def test_add_provider_from_str(self):
         provider_config = {
             "name": "my_provider",
             "type": "module_a",
-            "pool": "__primary__",
             "provider_id": 42,
             "config": {},
             "dependencies": {},
@@ -321,7 +213,6 @@ def test_add_provider_from_spec(self):
         provider_config = spec.ProviderSpec(
             name="my_provider",
             type="module_a",
-            pool=spec.PoolSpec(name="__primary__"),
             provider_id=42,
             config={},
             dependencies={},
diff --git a/python/mochi/bedrock/test_ssg_manager.py b/python/mochi/bedrock/test_ssg_manager.py
deleted file mode 100644
index 98d2ea7..0000000
--- a/python/mochi/bedrock/test_ssg_manager.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import unittest
-import pymargo.logging
-import mochi.bedrock.server as mbs
-import mochi.bedrock.spec as spec
-
-
-class TestSSGManager(unittest.TestCase):
-
-    def setUp(self):
-        config = {
-            "ssg": [
-                {
-                    "name": "my_group",
-                    "pool": "__primary__",
-                    "bootstrap": "init",
-                    "config": {},
-                    "swim": {}
-                }
-            ],
-            "libraries": {
-                "module_a": "./libModuleA.so"
-            },
-            "providers": [
-                {
-                    "name": "my_provider_A1",
-                    "type": "module_a",
-                    "provider_id": 1,
-                    "__if__": "$__ssg__.my_group.rank == 0"
-                },
-                {
-                    "name": "my_provider_A2",
-                    "type": "module_a",
-                    "provider_id": 2,
-                    "__if__": "$__ssg__.my_group.rank == 1"
-                }
-            ]
-        }
-        self.server = mbs.Server(address="na+sm", config=config)
-        self.server.margo.engine.logger.set_log_level(pymargo.logging.level.critical)
-
-    def tearDown(self):
-        self.server.finalize()
-        del self.server
-
-    def test_get_ssg_manager(self):
-        ssg = self.server.ssg
-        self.assertIsInstance(ssg, mbs.SSGManager)
-        self.assertEqual(len(ssg), 1)
-        self.assertIn("my_group", ssg)
-        self.assertNotIn("not_my_group", ssg)
-        ssg_A = ssg[0]
-        ssg_B = ssg["my_group"]
-        self.assertEqual(ssg_A.name, ssg_B.name)
-        self.assertEqual(ssg_A.type, ssg_B.type)
-        self.assertEqual(ssg_A.handle, ssg_B.handle)
-        with self.assertRaises(mbs.BedrockException):
-            ssg[1]
-        with self.assertRaises(mbs.BedrockException):
-            ssg["bla"]
-        self.assertIn("my_provider_A1", self.server.providers)
-        self.assertNotIn("my_provider_A2", self.server.providers)
-
-
-    def test_ssg_manager_config(self):
-        config = self.server.ssg.config
-        self.assertIsInstance(config, list)
-        self.assertEqual(len(config), 1)
-        ssg_1 = config[0]
-        self.assertIsInstance(ssg_1, dict)
-        for key in ["name", "pool", "credential", "bootstrap", "group_file", "swim"]:
-            self.assertIn(key, ssg_1)
-
-    def test_ssg_manager_spec(self):
-        spec_list = self.server.ssg.spec
-        self.assertIsInstance(spec_list, list)
-        for s in spec_list:
-            self.assertIsInstance(s, spec.SSGSpec)
-
-    def test_add_ssg_group(self):
-        ssg = self.server.ssg
-        ssg.create(
-            name="my_group_2",
-            pool="__primary__")
-        self.assertEqual(len(ssg), 2)
-        ssg_A = ssg[1]
-        ssg_B = ssg["my_group_2"]
-        self.assertEqual(ssg_A.name, ssg_B.name)
-        self.assertEqual(ssg_A.type, ssg_B.type)
-        self.assertEqual(ssg_A.handle, ssg_B.handle)
-
-    def test_resolve(self):
-        ssg = self.server.ssg
-        addr = ssg.resolve("ssg://my_group/0")
-        self.assertIsInstance(addr, pymargo.core.Address)
-        self.assertEqual(addr, self.server.margo.engine.address)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/python/src/py-bedrock-client.cpp b/python/src/py-bedrock-client.cpp
index d82499c..99ab738 100644
--- a/python/src/py-bedrock-client.cpp
+++ b/python/src/py-bedrock-client.cpp
@@ -35,15 +35,6 @@ PYBIND11_MODULE(pybedrock_client, m) {
              "Create a ServiceHandle instance",
              "address"_a,
              "provider_id"_a=0)
-        .def("make_service_group_handle_from_ssg_file",
-             [](const Client& client,
-                const std::string& groupfile,
-                uint16_t provider_id) {
-                return client.makeServiceGroupHandleFromSSGFile(groupfile, provider_id);
-             },
-             "Create a ServiceGroupHandle instance",
-             "group_file"_a,
-             "provider_id"_a=0)
         .def("make_service_group_handle",
              [](const Client& client,
                 const std::vector<std::string>& addresses,
@@ -53,15 +44,6 @@ PYBIND11_MODULE(pybedrock_client, m) {
              "Create a ServiceGroupHandle instance",
              "addresses"_a,
              "provider_id"_a=0)
-        .def("make_service_group_handle_from_ssg_group",
-             [](const Client& client,
-                uint64_t gid,
-                uint16_t provider_id) {
-                return client.makeServiceGroupHandleFromSSGGroup(gid, provider_id);
-             },
-             "Create a ServiceGroupHandle instance",
-             "group_id"_a,
-             "provider_id"_a=0)
         .def("make_service_group_handle_from_flock_file",
              [](const Client& client,
                 const std::string& groupfile,
@@ -93,10 +75,9 @@ PYBIND11_MODULE(pybedrock_client, m) {
             }, "script"_a)
         .def("load_module",
             [](const ServiceHandle& sh,
-               const std::string& name,
                const std::string& path) {
-                    sh.loadModule(name, path);
-            }, "name"_a, "path"_a)
+                    sh.loadModule(path);
+            }, "path"_a)
         .def("add_provider",
             [](const ServiceHandle& sh,
                const std::string& description) {
@@ -104,26 +85,6 @@ PYBIND11_MODULE(pybedrock_client, m) {
                     sh.addProvider(description, &provider_id_out);
                     return provider_id_out;
             }, "description"_a)
-        .def("change_provider_pool",
-             [](const ServiceHandle& sh,
-                const std::string& provider_name,
-                const std::string& pool_name) {
-                sh.changeProviderPool(provider_name, pool_name);
-             })
-        .def("add_client",
-            [](const ServiceHandle& sh,
-               const std::string& description) {
-                    sh.addClient(description);
-            }, "description"_a)
-        .def("add_abtio_instance",
-            [](const ServiceHandle& sh,
-               const std::string& description) {
-                    sh.addABTioInstance(description);
-            }, "description"_a = std::string("{}"))
-        .def("add_ssg_group", [](const ServiceHandle& sh, const std::string& config) {
-                sh.addSSGgroup(config);
-            },
-            "description"_a)
         .def("add_pool", [](const ServiceHandle& sh, const std::string& config) {
                 sh.addPool(config);
             },
diff --git a/python/src/py-bedrock-server.cpp b/python/src/py-bedrock-server.cpp
index 6fe2ee3..e76cf77 100644
--- a/python/src/py-bedrock-server.cpp
+++ b/python/src/py-bedrock-server.cpp
@@ -8,9 +8,6 @@
 #include "pybind11_json/pybind11_json.hpp"
 #include <bedrock/Server.hpp>
 #include <bedrock/Exception.hpp>
-#ifdef ENABLE_SSG
-  #include <ssg.h>
-#endif
 
 namespace py11 = pybind11;
 using namespace pybind11::literals;
@@ -38,8 +35,6 @@ PYBIND11_MODULE(pybedrock_server, m) {
             [](std::shared_ptr<NamedDependency> nd) { return nd->getName(); })
         .def_property_readonly("type",
             [](std::shared_ptr<NamedDependency> nd) { return nd->getType(); })
-        .def_property_readonly("handle",
-            [](std::shared_ptr<NamedDependency> nd) { return nd->getHandle<intptr_t>(); })
     ;
 
     py11::class_<ProviderDependency, std::shared_ptr<ProviderDependency>> (m, "ProviderDependency", named_dep)
@@ -81,22 +76,10 @@ PYBIND11_MODULE(pybedrock_server, m) {
              [](std::shared_ptr<Server> server) {
                 return server->getMargoManager();
              })
-        .def_property_readonly("abtio_manager",
-             [](std::shared_ptr<Server> server) {
-                return server->getABTioManager();
-             })
         .def_property_readonly("provider_manager",
              [](std::shared_ptr<Server> server) {
                 return server->getProviderManager();
              })
-        .def_property_readonly("client_manager",
-             [](std::shared_ptr<Server> server) {
-                return server->getClientManager();
-             })
-        .def_property_readonly("ssg_manager",
-             [](std::shared_ptr<Server> server) {
-                return server->getSSGManager();
-             })
     ;
 
     py11::class_<MargoManager> (m, "MargoManager")
@@ -137,63 +120,6 @@ PYBIND11_MODULE(pybedrock_server, m) {
         .def_property_readonly("num_xstreams", &MargoManager::getNumXstreams)
     ;
 
-    py11::class_<SSGManager> (m, "SSGManager")
-        .def_property_readonly("config", [](const SSGManager& manager) {
-            return manager.getCurrentConfig().dump();
-        })
-        .def_property_readonly("num_groups", &SSGManager::getNumGroups)
-        .def("get_group", [](const SSGManager& ssg, const std::string& name) {
-                return ssg.getGroup(name);
-             }, "name_a")
-        .def("get_group", [](const SSGManager& ssg, size_t index) {
-                return ssg.getGroup(index);
-             }, "index_a")
-        .def("add_group",
-             [](SSGManager& ssg,
-                const std::string& name,
-                const py11::dict& config,
-                const std::shared_ptr<NamedDependency>& pool,
-                const std::string& bootstrap_method,
-                const std::string& group_file,
-                int64_t credential) {
-#ifdef ENABLE_SSG
-                ssg_group_config_t cfg = SSG_GROUP_CONFIG_INITIALIZER;
-                cfg.ssg_credential = credential;
-#define GET_SSG_FIELD(__field__) do { \
-                if(config.contains(#__field__)) \
-                    cfg.swim_##__field__ = config[#__field__].cast<decltype(cfg.swim_##__field__)>(); \
-                } while(0)
-                GET_SSG_FIELD(period_length_ms);
-                GET_SSG_FIELD(suspect_timeout_periods);
-                GET_SSG_FIELD(subgroup_member_count);
-                GET_SSG_FIELD(disabled);
-#undef GET_SSG_FIELD
-                return ssg.addGroup(name, cfg, pool, bootstrap_method, group_file);
-#else
-                throw Exception{"Bedrock was not compiled with SSG support"};
-#endif
-             }, "name"_a, "swim"_a=py11::dict{},
-                "pool"_a=nullptr, "bootstrap"_a="init",
-                "group_file"_a="", "credential"_a=-1)
-        .def("resolve_address", [](const SSGManager& ssg, const std::string& address) {
-                return ADDR2CAPSULE(ssg.resolveAddress(address));
-            }, "address"_a)
-    ;
-
-    py11::class_<ABTioManager> (m, "ABTioManager")
-        .def_property_readonly("config", [](const ABTioManager& manager) {
-            return manager.getCurrentConfig().dump();
-        })
-        .def_property_readonly("num_abtio_instances", &ABTioManager::numABTioInstances)
-        .def("get_abtio_instance", [](const ABTioManager& abtio, const std::string& name) {
-            return abtio.getABTioInstance(name);
-        }, "name"_a)
-        .def("get_abtio_instance", [](const ABTioManager& abtio, size_t index) {
-            return abtio.getABTioInstance(index);
-        }, "index"_a)
-        .def("add_abtio_instance", &ABTioManager::addABTioInstance)
-    ;
-
     py11::class_<ProviderManager> (m, "ProviderManager")
         .def_property_readonly("config", &ProviderManager::getCurrentConfig)
         .def_property_readonly("num_providers", &ProviderManager::numProviders)
@@ -211,9 +137,6 @@ PYBIND11_MODULE(pybedrock_server, m) {
         .def("add_provider",
              &ProviderManager::addProviderFromJSON,
              "description"_a)
-        .def("change_pool",
-             &ProviderManager::changeProviderPool,
-             "provider"_a, "pool"_a)
         .def("migrate_provider",
              &ProviderManager::migrateProvider,
              "provider"_a, "dest_addr"_a, "dest_provider_id"_a,
@@ -225,29 +148,4 @@ PYBIND11_MODULE(pybedrock_server, m) {
              &ProviderManager::restoreProvider,
              "provider"_a, "src_path"_a, "restore_config"_a)
     ;
-
-    py11::class_<ClientManager> (m, "ClientManager")
-        .def_property_readonly("config", &ClientManager::getCurrentConfig)
-        .def("get_client", [](const ClientManager& cm, const std::string& name) {
-                return cm.getClient(name);
-             },
-             "name"_a)
-        .def("get_client", [](const ClientManager& cm, size_t index) {
-                return cm.getClient(index);
-             },
-             "index"_a)
-        .def_property_readonly("num_clients", &ClientManager::numClients)
-        .def("remove_client", [](ClientManager& cm, const std::string& name) {
-                return cm.removeClient(name);
-             },
-             "name"_a)
-        .def("remove_client", [](ClientManager& cm, size_t index) {
-                return cm.removeClient(index);
-             },
-             "index"_a)
-        .def("get_client_or_create", &ClientManager::getOrCreateAnonymous,
-             "type"_a)
-        .def("add_client", &ClientManager::addClientFromJSON,
-             "description"_a)
-    ;
 }
diff --git a/spack.yaml b/spack.yaml
index 086be56..d124f4d 100644
--- a/spack.yaml
+++ b/spack.yaml
@@ -5,9 +5,6 @@ spack:
   - mochi-margo
   - mochi-thallium
   - mochi-bedrock-module-api
-  - mochi-abt-io
-  - mochi-ssg+mpi
-  - mochi-mona
   - mochi-flock
   - nlohmann-json
   - nlohmann-json-schema-validator
@@ -20,7 +17,6 @@ spack:
   - py-attrs
   - py-typer
   - py-rich
-  - py-mochi-ssg
   - py-configspace
   concretizer:
     unify: true
diff --git a/src/ABTioManager.cpp b/src/ABTioManager.cpp
deleted file mode 100644
index 4532301..0000000
--- a/src/ABTioManager.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#include <bedrock/ABTioManager.hpp>
-#include <bedrock/MargoManager.hpp>
-#include <bedrock/Jx9Manager.hpp>
-#include <bedrock/DetailedException.hpp>
-#include "JsonUtil.hpp"
-#include "ABTioManagerImpl.hpp"
-#include <margo.h>
-
-namespace tl = thallium;
-
-namespace bedrock {
-
-using nlohmann::json;
-
-ABTioManager::ABTioManager(const MargoManager& margoCtx,
-                           const Jx9Manager& jx9,
-                           const json& config)
-: self(std::make_shared<ABTioManagerImpl>()) {
-    self->m_margo_manager = margoCtx;
-    self->m_jx9_manager   = jx9;
-    if (config.is_null()) return;
-#ifndef ENABLE_ABT_IO
-    if (!(config.is_array() && config.empty()))
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Configuration has an \"abt_io\" field but Bedrock wasn't compiled with ABT-IO support");
-#else
-    if (!config.is_array()) {
-        throw BEDROCK_DETAILED_EXCEPTION("\"abt_io\" field in configuration should be an array");
-    }
-    for(auto& description : config) {
-        addABTioInstanceFromJSON(description);
-    }
-#endif
-}
-
-// LCOV_EXCL_START
-
-ABTioManager::ABTioManager(const ABTioManager&) = default;
-
-ABTioManager::ABTioManager(ABTioManager&&) = default;
-
-ABTioManager& ABTioManager::operator=(const ABTioManager&) = default;
-
-ABTioManager& ABTioManager::operator=(ABTioManager&&) = default;
-
-ABTioManager::~ABTioManager() = default;
-
-ABTioManager::operator bool() const { return static_cast<bool>(self); }
-
-// LCOV_EXCL_STOP
-
-std::shared_ptr<NamedDependency>
-ABTioManager::getABTioInstance(const std::string& name) const {
-#ifndef ENABLE_ABT_IO
-    (void)name;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiler with ABT-IO support");
-#else
-    auto it = std::find_if(self->m_instances.begin(), self->m_instances.end(),
-                           [&name](const auto& p) { return p->getName() == name; });
-    if (it == self->m_instances.end())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find ABT-IO instance \"{}\"", name);
-    return *it;
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-ABTioManager::getABTioInstance(size_t index) const {
-#ifndef ENABLE_ABT_IO
-    (void)index;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiler with ABT-IO support");
-#else
-    if (index >= self->m_instances.size())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find ABT-IO instance at index {}", index);
-    return self->m_instances[index];
-#endif
-}
-
-size_t ABTioManager::numABTioInstances() const {
-#ifndef ENABLE_ABT_IO
-    return 0;
-#else
-    return self->m_instances.size();
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-ABTioManager::addABTioInstance(const std::string&                      name,
-                               const std::shared_ptr<NamedDependency>& pool,
-                               const json&                             config) {
-#ifndef ENABLE_ABT_IO
-    (void)name;
-    (void)pool;
-    (void)config;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiled with ABT-IO support");
-#else
-    json     abt_io_config;
-    // check if the name doesn't already exist
-    auto it = std::find_if(
-        self->m_instances.begin(), self->m_instances.end(),
-        [&name](const auto& instance) { return instance->getName() == name; });
-    if (it != self->m_instances.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "ABT-IO instance name \"{}\" already used",
-            name);
-    }
-    // get the config of this ABT-IO instance
-    if (!config.is_object() && !config.is_null()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "\"config\" field in ABT-IO instance configuration should be an object");
-    }
-    // all good, can instanciate
-    abt_io_init_info abt_io_info;
-    auto configStr            = config.is_null() ? std::string{"{}"} : config.dump();
-    abt_io_info.json_config   = configStr.c_str();
-    abt_io_info.progress_pool = pool->getHandle<ABT_pool>();
-    abt_io_instance_id abt_io = abt_io_init_ext(&abt_io_info);
-    if (abt_io == ABT_IO_INSTANCE_NULL) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not initialize ABT-IO instance \"{}\"", name);
-    }
-    auto entry = std::make_shared<ABTioEntry>(name, abt_io, pool);
-    self->m_instances.push_back(entry);
-    return entry;
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-ABTioManager::addABTioInstanceFromJSON(const json& description) {
-    static const json configSchema = R"(
-    {
-        "$schema": "https://json-schema.org/draft/2019-09/schema",
-        "type": "object",
-        "properties": {
-            "name": {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-            "pool": {"oneOf": [
-                {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-                {"type": "integer", "minimum": 0 }
-            ]},
-            "config": {"type": "object"}
-        },
-        "required": ["name"]
-    }
-    )"_json;
-    static const JsonValidator validator{configSchema};
-    validator.validate(description, "ABTioManager");
-    auto name = description["name"].get<std::string>();
-    auto config = description.value("config", json::object());
-    std::shared_ptr<NamedDependency> pool;
-    // find pool
-    if(description.contains("pool") && description["pool"].is_number()) {
-        pool = MargoManager(self->m_margo_manager)
-                .getPool(description["pool"].get<uint32_t>());
-    } else {
-        pool = MargoManager(self->m_margo_manager)
-                .getPool(description.value("pool", "__primary__"));
-    }
-    return addABTioInstance(name, pool, config);
-}
-
-json ABTioManager::getCurrentConfig() const {
-#ifndef ENABLE_ABT_IO
-    return json::array();
-#else
-    return self->makeConfig();
-#endif
-}
-
-} // namespace bedrock
diff --git a/src/ABTioManagerImpl.hpp b/src/ABTioManagerImpl.hpp
deleted file mode 100644
index 23aee55..0000000
--- a/src/ABTioManagerImpl.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_ABTIO_CONTEXT_IMPL_H
-#define __BEDROCK_ABTIO_CONTEXT_IMPL_H
-
-#include "bedrock/MargoManager.hpp"
-#include "bedrock/ABTioManager.hpp"
-#include "bedrock/NamedDependency.hpp"
-#include "MargoManagerImpl.hpp"
-#include "Jx9ManagerImpl.hpp"
-#include <nlohmann/json.hpp>
-#include <spdlog/spdlog.h>
-#include <string>
-#include <vector>
-#include <unordered_map>
-
-#ifdef ENABLE_ABT_IO
-#include <abt-io.h>
-#else
-inline char* abt_io_get_config(abt_io_instance_id) { return nullptr; }
-inline void abt_io_finalize(abt_io_instance_id) {}
-#endif
-
-
-namespace bedrock {
-
-using nlohmann::json;
-
-class ABTioEntry : public NamedDependency {
-
-    public:
-
-    std::shared_ptr<NamedDependency> pool;
-
-    json makeConfig() const {
-        json config      = json::object();
-        config["name"]   = getName();
-        config["pool"]   = pool->getName();
-        auto c           = abt_io_get_config(getHandle<abt_io_instance_id>());
-        config["config"] = c ? json::parse(c) : json::object();
-        free(c);
-        return config;
-    }
-
-    template<typename S>
-    ABTioEntry(
-        S&& name,
-        abt_io_instance_id abt_io_id,
-        std::shared_ptr<NamedDependency> p)
-    : NamedDependency(
-        std::forward<S>(name),
-        "abt_io",
-        abt_io_id,
-        releaseABTioEntry)
-    , pool(std::move(p))
-    {}
-
-    ABTioEntry(const ABTioEntry&) = delete;
-    ABTioEntry(ABTioEntry&& other) = delete;
-
-    static void releaseABTioEntry(void* args) {
-        auto abt_io_id = static_cast<abt_io_instance_id>(args);
-        if(!abt_io_id) return;
-        abt_io_finalize(abt_io_id);
-    }
-
-    ~ABTioEntry() {
-        spdlog::trace("Freeing ABT-IO instance {}", getName());
-    }
-};
-
-class ABTioManagerImpl {
-
-  public:
-    std::shared_ptr<MargoManagerImpl>        m_margo_manager;
-    std::shared_ptr<Jx9ManagerImpl>          m_jx9_manager;
-    std::vector<std::shared_ptr<ABTioEntry>> m_instances;
-
-    json makeConfig() const {
-        json config = json::array();
-        for (auto& i : m_instances) { config.push_back(i->makeConfig()); }
-        return config;
-    }
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f52a303..af926d6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,12 +1,8 @@
 # set source files
 set (server-src-files
      Server.cpp
-     ABTioManager.cpp
      MargoManager.cpp
-     MonaManager.cpp
-     SSGManager.cpp
      ProviderManager.cpp
-     ClientManager.cpp
      DependencyFinder.cpp
      MargoLogging.cpp
      Jx9Manager.cpp
@@ -46,9 +42,6 @@ target_link_libraries (bedrock-server
     nlohmann_json::nlohmann_json
     spdlog::spdlog
     fmt::fmt
-    ${OPTIONAL_ABT_IO}
-    ${OPTIONAL_MONA}
-    ${OPTIONAL_SSG}
     ${OPTIONAL_MPI})
 target_include_directories (bedrock-server PUBLIC $<INSTALL_INTERFACE:include>)
 target_include_directories (bedrock-server BEFORE PUBLIC
@@ -65,7 +58,6 @@ add_library (bedrock::client ALIAS bedrock-client)
 target_compile_options (bedrock-client PUBLIC -Wall -Wextra -Wpedantic)
 target_link_libraries (bedrock-client
     PRIVATE coverage_config
-    ${OPTIONAL_SSG}
     ${OPTIONAL_FLOCK}
     PUBLIC
     thallium
diff --git a/src/Client.cpp b/src/Client.cpp
index 8ff7a49..fe558e9 100644
--- a/src/Client.cpp
+++ b/src/Client.cpp
@@ -14,9 +14,6 @@
 #include "ServiceGroupHandleImpl.hpp"
 
 #include <thallium/serialization/stl/string.hpp>
-#ifdef ENABLE_SSG
-#include <ssg.h>
-#endif
 
 namespace tl = thallium;
 
@@ -59,24 +56,6 @@ ServiceHandle Client::makeServiceHandle(const std::string& address,
     return ServiceHandle(service_impl);
 }
 
-ServiceGroupHandle Client::makeServiceGroupHandleFromSSGFile(
-        const std::string& groupfile,
-        uint16_t provider_id) const {
-    auto impl = ServiceGroupHandleImpl::FromSSGfile(self, groupfile, provider_id);
-    auto result = ServiceGroupHandle{std::move(impl)};
-    result.refresh();
-    return result;
-}
-
-ServiceGroupHandle Client::makeServiceGroupHandleFromSSGGroup(
-        uint64_t gid,
-        uint16_t provider_id) const {
-    auto impl = ServiceGroupHandleImpl::FromSSGid(self, gid, provider_id);
-    auto result = ServiceGroupHandle{std::move(impl)};
-    result.refresh();
-    return result;
-}
-
 ServiceGroupHandle Client::makeServiceGroupHandleFromFlockFile(
         const std::string& groupfile,
         uint16_t provider_id) const {
diff --git a/src/ClientImpl.hpp b/src/ClientImpl.hpp
index 84577a0..f51a068 100644
--- a/src/ClientImpl.hpp
+++ b/src/ClientImpl.hpp
@@ -27,7 +27,6 @@ class ClientImpl {
     tl::remote_procedure m_restore_provider;
     tl::remote_procedure m_add_client;
     tl::remote_procedure m_add_abtio;
-    tl::remote_procedure m_add_ssg_group;
     tl::remote_procedure m_add_pool;
     tl::remote_procedure m_add_xstream;
     tl::remote_procedure m_remove_pool;
@@ -44,7 +43,6 @@ class ClientImpl {
       m_restore_provider(m_engine.define("bedrock_restore_provider")),
       m_add_client(m_engine.define("bedrock_add_client")),
       m_add_abtio(m_engine.define("bedrock_add_abtio")),
-      m_add_ssg_group(m_engine.define("bedrock_add_ssg_group")),
       m_add_pool(m_engine.define("bedrock_add_pool")),
       m_add_xstream(m_engine.define("bedrock_add_xstream")),
       m_remove_pool(m_engine.define("bedrock_remove_pool")),
diff --git a/src/ClientManager.cpp b/src/ClientManager.cpp
deleted file mode 100644
index 4792222..0000000
--- a/src/ClientManager.cpp
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#include <bedrock/ClientManager.hpp>
-#include <bedrock/ModuleContext.hpp>
-#include <bedrock/AbstractServiceFactory.hpp>
-#include <bedrock/DependencyFinder.hpp>
-#include <bedrock/DetailedException.hpp>
-
-#include "ClientManagerImpl.hpp"
-#include "JsonUtil.hpp"
-
-#include <thallium/serialization/stl/string.hpp>
-#include <thallium/serialization/stl/vector.hpp>
-#include <spdlog/spdlog.h>
-#include <nlohmann/json.hpp>
-#include <cctype>
-
-namespace tl = thallium;
-
-namespace bedrock {
-
-using namespace std::string_literals;
-using nlohmann::json;
-
-ClientManager::ClientManager(const MargoManager& margo,
-                             const Jx9Manager& jx9,
-                             uint16_t provider_id,
-                             const std::shared_ptr<NamedDependency>& pool)
-: self(std::make_shared<ClientManagerImpl>(margo.getThalliumEngine(),
-                                           provider_id, tl::pool(pool->getHandle<ABT_pool>()))) {
-    self->m_margo_manager = margo;
-    self->m_jx9_manager   = jx9;
-}
-
-// LCOV_EXCL_START
-
-ClientManager::ClientManager(const ClientManager&) = default;
-
-ClientManager::ClientManager(ClientManager&&) = default;
-
-ClientManager& ClientManager::operator=(const ClientManager&) = default;
-
-ClientManager& ClientManager::operator=(ClientManager&&) = default;
-
-ClientManager::~ClientManager() = default;
-
-ClientManager::operator bool() const { return static_cast<bool>(self); }
-
-// LCOV_EXCL_STOP
-
-void ClientManager::setDependencyFinder(const DependencyFinder& finder) {
-    self->m_dependency_finder = finder;
-}
-
-size_t ClientManager::numClients() const {
-    std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-    return self->m_clients.size();
-}
-
-std::shared_ptr<NamedDependency> ClientManager::getClient(const std::string& name) const {
-    std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-    auto                       it = self->findByName(name);
-    if (it == self->m_clients.end())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find client \"{}\"", name);
-    return *it;
-}
-
-std::shared_ptr<NamedDependency> ClientManager::getClient(size_t index) const {
-    std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-    if (index >= self->m_clients.size())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find client at index {}", index);
-    return self->m_clients[index];
-}
-
-std::shared_ptr<NamedDependency> ClientManager::getOrCreateAnonymous(const std::string& type) {
-    {
-        std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-        for (const auto& client : self->m_clients) {
-            if (client->getType() == type) {
-                return client;
-            }
-        }
-        // no client of this type found, try creating one with name
-        // __type_client__ first we need to check whether we have the module for
-        // such a client
-        auto service_factory = ModuleContext::getServiceFactory(type);
-        if (!service_factory) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Could not find service factory for client type \"{}\"", type);
-        }
-        // find out if such a client has required dependencies
-        for (const auto& dependency :
-             service_factory->getClientDependencies("{}")) {
-            if (dependency.flags & BEDROCK_REQUIRED)
-                throw BEDROCK_DETAILED_EXCEPTION(
-                    "Could not create default client of type \"{}\" because"
-                    " it requires dependency \"{}\"",
-                    type, dependency.name);
-        }
-    }
-
-    // we can create the client
-    std::string name = "__"s + type + "_client__";
-    ResolvedDependencyMap dependencies;
-
-    addClient(name, type, json::object(), dependencies);
-    // get the client
-    auto it  = self->findByName(name);
-    return *it;
-}
-
-std::shared_ptr<NamedDependency>
-ClientManager::addClient(const std::string&              name,
-                         const std::string&              type,
-                         const json&                     config,
-                         const ResolvedDependencyMap&    dependencies,
-                         const std::vector<std::string>& tags) {
-    std::shared_ptr<ClientEntry> entry;
-    auto service_factory = ModuleContext::getServiceFactory(type);
-    if (!service_factory) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find service factory for client type \"{}\"", type);
-    }
-
-    {
-        std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-        auto                       it = self->findByName(name);
-        if (it != self->m_clients.end()) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Name \"{}\" is already used by another client", name);
-        }
-
-        auto margoCtx = MargoManager(self->m_margo_manager);
-
-        FactoryArgs args;
-        args.name         = name;
-        args.mid          = margoCtx.getMargoInstance();
-        args.engine       = margoCtx.getThalliumEngine();
-        args.pool         = ABT_POOL_NULL;
-        args.config       = config.dump();
-        args.provider_id  = std::numeric_limits<uint16_t>::max();
-        args.tags         = tags;
-        args.dependencies = dependencies;
-
-        auto handle = service_factory->initClient(args);
-
-        entry = std::make_shared<ClientEntry>(
-            name, type,
-            handle, service_factory, dependencies, tags);
-
-        spdlog::trace("Registered client {} of type {}", name, type);
-
-        self->m_clients.push_back(entry);
-    }
-    self->m_clients_cv.notify_all();
-    return entry;
-}
-
-void ClientManager::removeClient(const std::string& name) {
-    std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-    auto                       it = self->findByName(name);
-    if (it == self->m_clients.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find client with name \"{}\"", name);
-    }
-    if(it->use_count() > 1) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Cannot destroy client \"{}\" as it is used as dependency",
-            (*it)->getName());
-    }
-    self->m_clients.erase(it);
-}
-
-void ClientManager::removeClient(size_t index) {
-    std::lock_guard<tl::mutex> lock(self->m_clients_mtx);
-    if (index >= self->m_clients.size())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find client at index {}", index);
-    if(self->m_clients[index].use_count() > 1) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Cannot destroy client at index {} as it is used as dependency",
-            index);
-    }
-    self->m_clients.erase(self->m_clients.begin() + index);
-}
-
-std::shared_ptr<NamedDependency>
-ClientManager::addClientFromJSON(const json& description) {
-    auto dependencyFinder = DependencyFinder(self->m_dependency_finder);
-    static const json configSchema = R"(
-    {
-        "$schema": "https://json-schema.org/draft/2019-09/schema",
-        "type": "object",
-        "properties": {
-            "name": {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-            "type": {"type": "string"},
-            "tags": {
-                "type": "array",
-                "items": {"type": "string"}
-            },
-            "dependencies": {
-                "type": "object",
-                "additionalProperties": {
-                    "anyOf": [
-                        {"type": "string"},
-                        {"type": "array", "items": {"type": "string"}}
-                    ]
-                }
-            },
-            "config": {"type": "object"}
-        },
-        "required": ["name", "type"]
-    }
-    )"_json;
-    static const JsonValidator validator{configSchema};
-    validator.validate(description, "ClientManager");
-
-    auto& name = description["name"].get_ref<const std::string&>();
-    auto& type = description["type"].get_ref<const std::string&>();
-
-    auto service_factory = ModuleContext::getServiceFactory(type);
-    if (!service_factory) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find service factory for client type \"{}\"", type);
-    }
-
-    auto config = description.value("config", json::object());
-    auto configStr = config.is_null() ? std::string{"{}"} : config.dump();
-    std::vector<std::string> tags;
-    for(auto& tag : description.value("tags", json::array())) {
-        tags.push_back(tag.get<std::string>());
-    }
-
-    auto deps_from_config = description.value("dependencies", json::object());
-    ResolvedDependencyMap resolved_dependency_map;
-
-    for (const auto& dependency : service_factory->getClientDependencies(configStr.c_str())) {
-        spdlog::trace("Resolving dependency {}", dependency.name);
-        if (deps_from_config.contains(dependency.name)) {
-            auto dep_config = deps_from_config[dependency.name];
-            if (!(dependency.flags & BEDROCK_ARRAY)) {
-                if (dep_config.is_array() && dep_config.size() == 1) {
-                    // an array of length 1 can be converted into a single string
-                    dep_config = dep_config[0];
-                }
-                if (!dep_config.is_string()) {
-                    throw BEDROCK_DETAILED_EXCEPTION("Dependency \"{}\" should be a string",
-                                    dependency.name);
-                }
-                auto ptr = dependencyFinder.find(
-                        dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
-                        dep_config.get<std::string>(), nullptr);
-                resolved_dependency_map[dependency.name].dependencies.push_back(ptr);
-                resolved_dependency_map[dependency.name].is_array = false;
-            } else {
-                if (dep_config.is_string()) {
-                    // a single string can be converted into an array of size 1
-                    auto tmp_array = json::array();
-                    tmp_array.push_back(dep_config);
-                    dep_config = tmp_array;
-                }
-                if (!dep_config.is_array()) {
-                    throw BEDROCK_DETAILED_EXCEPTION("Dependency \"{}\" should be an array",
-                                    dependency.name);
-                }
-                std::vector<std::string> deps;
-                for (const auto& elem : dep_config) {
-                    auto ptr = dependencyFinder.find(
-                            dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
-                            elem.get<std::string>(), nullptr);
-                    resolved_dependency_map[dependency.name].dependencies.push_back(ptr);
-                    resolved_dependency_map[dependency.name].is_array = true;
-                }
-            }
-        } else if (dependency.flags & BEDROCK_REQUIRED) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Missing dependency \"{}\" of type \"{}\" in provider configuration",
-                dependency.name, dependency.type);
-        }
-    }
-
-    return addClient(name, type, config, resolved_dependency_map, tags);
-}
-
-void ClientManager::addClientListFromJSON(const json& list) {
-    if (list.is_null()) { return; }
-    if (!list.is_array()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Invalid JSON configuration passed to "
-            "ClientManager::addClientListFromJSON (expected array)");
-    }
-    for (const auto& description : list) {
-        addClientFromJSON(description);
-    }
-}
-
-json ClientManager::getCurrentConfig() const {
-    return self->makeConfig();
-}
-
-} // namespace bedrock
diff --git a/src/ClientManagerImpl.hpp b/src/ClientManagerImpl.hpp
deleted file mode 100644
index 855028f..0000000
--- a/src/ClientManagerImpl.hpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_CLIENT_MANAGER_IMPL_H
-#define __BEDROCK_CLIENT_MANAGER_IMPL_H
-
-#include "MargoManagerImpl.hpp"
-#include "bedrock/Jx9Manager.hpp"
-#include "bedrock/RequestResult.hpp"
-#include "bedrock/AbstractServiceFactory.hpp"
-#include "bedrock/ClientDescriptor.hpp"
-#include "bedrock/ClientManager.hpp"
-#include "DependencyFinderImpl.hpp"
-
-#include <thallium.hpp>
-#include <nlohmann/json.hpp>
-#include <spdlog/spdlog.h>
-
-#include <algorithm>
-
-namespace bedrock {
-
-using nlohmann::json;
-using namespace std::string_literals;
-namespace tl = thallium;
-
-class ClientEntry : public NamedDependency {
-
-    public:
-
-    AbstractServiceFactory*  factory;
-    ResolvedDependencyMap    dependencies;
-    std::vector<std::string> tags;
-
-    ClientEntry(std::string name, std::string type,
-                void* handle, AbstractServiceFactory* f,
-                ResolvedDependencyMap deps,
-                std::vector<std::string> _tags)
-    : NamedDependency(
-        std::move(name),
-        std::move(type),
-        handle,
-        [f](void* args) {
-            if(f) f->finalizeClient(args);
-        })
-    , factory(f)
-    , dependencies(deps)
-    , tags(std::move(_tags)) {}
-
-    json makeConfig() const {
-        auto c            = json::object();
-        c["name"]         = getName();
-        c["type"]         = getType();
-        c["config"]       = json::parse(factory->getClientConfig(getHandle<void*>()));
-        c["tags"]         = json::array();
-        for(auto& t : tags) c["tags"].push_back(t);
-        c["dependencies"] = json::object();
-        auto& d           = c["dependencies"];
-        for (auto& p : dependencies) {
-            auto& dep_name  = p.first;
-            auto& dep_group = p.second;
-            if (dep_group.dependencies.size() == 0) continue;
-            if (!dep_group.is_array) {
-                d[dep_name] = dep_group.dependencies[0]->getName();
-            } else {
-                d[dep_name] = json::array();
-                for (auto& x : dep_group.dependencies) {
-                    d[dep_name].push_back(x->getName());
-                }
-            }
-        }
-        return c;
-    }
-};
-
-class ClientManagerImpl
-: public tl::provider<ClientManagerImpl>,
-  public std::enable_shared_from_this<ClientManagerImpl> {
-
-  public:
-    std::shared_ptr<DependencyFinderImpl>     m_dependency_finder;
-    std::vector<std::shared_ptr<ClientEntry>> m_clients;
-    mutable tl::mutex                         m_clients_mtx;
-    mutable tl::condition_variable            m_clients_cv;
-
-    std::shared_ptr<MargoManagerImpl> m_margo_manager;
-    std::shared_ptr<Jx9ManagerImpl>   m_jx9_manager;
-
-    tl::remote_procedure m_lookup_client;
-    tl::remote_procedure m_add_client;
-
-    ClientManagerImpl(const tl::engine& engine, uint16_t provider_id,
-                      const tl::pool& pool)
-    : tl::provider<ClientManagerImpl>(engine, provider_id)
-    , m_lookup_client(define("bedrock_lookup_client",
-                             &ClientManagerImpl::lookupClientRPC, pool))
-    , m_add_client(define("bedrock_add_client",
-                          &ClientManagerImpl::addClientRPC, pool))
-    {}
-
-    auto findByName(const std::string& spec) {
-        auto it = std::find_if(
-            m_clients.begin(), m_clients.end(),
-            [&spec](const std::shared_ptr<ClientEntry>& item) { return item->getName() == spec; });
-        return it;
-    }
-
-    json makeConfig() const {
-        auto                       config = json::array();
-        std::lock_guard<tl::mutex> lock(m_clients_mtx);
-        for (auto& p : m_clients) { config.push_back(p->makeConfig()); }
-        return config;
-    }
-
-  private:
-    void lookupClientRPC(const tl::request& req, const std::string& name,
-                         double timeout) {
-        double        t1      = tl::timer::wtime();
-        RequestResult<ClientDescriptor> result;
-        std::unique_lock<tl::mutex> lock(m_clients_mtx);
-        auto it = findByName(name);
-        if (it == m_clients.end() && timeout > 0) {
-            m_clients_cv.wait(lock, [this, &name, &it, t1, timeout]() {
-                // FIXME will not actually wake up after timeout
-                double t2 = tl::timer::wtime();
-                it = findByName(name);
-                return (t2 - t1 > timeout) || (it != m_clients.end());
-            });
-        }
-        if (it != m_clients.end()) {
-            result.value().name = (*it)->getName();
-            result.value().type = (*it)->getType();
-        } else {
-            result.error()
-                = "Could not find client with name \""s + name + "\"";
-        }
-        req.respond(result);
-    }
-
-    void addClientRPC(const tl::request& req, const std::string& description) {
-        RequestResult<bool> result;
-        json                jsonconfig;
-        try {
-            if (!description.empty())
-                jsonconfig = json::parse(description);
-            else
-                jsonconfig = json::object();
-        } catch (const std::exception& ex) {
-            result.error()   = "Invalid JSON configuration for client";
-            result.success() = false;
-            req.respond(result);
-            return;
-        }
-        try {
-            ClientManager(shared_from_this()).addClientFromJSON(jsonconfig);
-        } catch (const Exception& ex) {
-            result.error()   = ex.what();
-            result.success() = false;
-        }
-        req.respond(result);
-    }
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/src/DependencyFinder.cpp b/src/DependencyFinder.cpp
index 8c1f8fd..243a650 100644
--- a/src/DependencyFinder.cpp
+++ b/src/DependencyFinder.cpp
@@ -5,9 +5,10 @@
  */
 #include "DependencyFinderImpl.hpp"
 #include "bedrock/DependencyFinder.hpp"
-#include "bedrock/ModuleContext.hpp"
-#include "bedrock/AbstractServiceFactory.hpp"
+#include "bedrock/ModuleManager.hpp"
+#include "bedrock/AbstractComponent.hpp"
 #include "bedrock/Exception.hpp"
+#include "bedrock/ProviderHandle.hpp"
 #include <thallium.hpp>
 #include <cctype>
 #include <regex>
@@ -18,19 +19,11 @@ namespace bedrock {
 
 DependencyFinder::DependencyFinder(const MPIEnv&          mpi,
                                    const MargoManager&    margo,
-                                   const ABTioManager&    abtio,
-                                   const SSGManager&      ssg,
-                                   const MonaManager&     mona,
-                                   const ProviderManager& pmanager,
-                                   const ClientManager&   cmanager)
+                                   const ProviderManager& pmanager)
 : self(std::make_shared<DependencyFinderImpl>(margo.getMargoInstance())) {
     self->m_mpi              = mpi.self;
     self->m_margo_context    = margo;
-    self->m_abtio_context    = abtio.self;
-    self->m_ssg_context      = ssg.self;
-    self->m_mona_context     = mona.self;
     self->m_provider_manager = pmanager.self;
-    self->m_client_manager   = cmanager.self;
 }
 
 // LCOV_EXCL_START
@@ -51,8 +44,9 @@ DependencyFinder::operator bool() const { return static_cast<bool>(self); }
 // LCOV_EXCL_STOP
 
 std::shared_ptr<NamedDependency> DependencyFinder::find(
-        const std::string& type, int32_t kind,
-        const std::string& spec, std::string* resolved) const {
+        const std::string& type,
+        const std::string& spec,
+        std::string* resolved) const {
     spdlog::trace("DependencyFinder search for {} of type {}", spec, type);
 
     if (type == "pool") { // Argobots pool
@@ -73,50 +67,7 @@ std::shared_ptr<NamedDependency> DependencyFinder::find(
         if (resolved) { *resolved = spec; }
         return xstream;
 
-    } else if (type == "abt_io") { // ABTIO instance
-
-        auto abtio_manager_impl = self->m_abtio_context.lock();
-        if (!abtio_manager_impl) {
-            throw Exception("Could not resolve ABT-IO dependency: no ABTioManager found");
-        }
-        auto abt_io = ABTioManager(abtio_manager_impl).getABTioInstance(spec);
-        if (resolved) { *resolved = spec; }
-        return abt_io;
-
-    } else if (type == "mona") { // MoNA instance
-
-        auto mona_manager_impl = self->m_mona_context.lock();
-        if (!mona_manager_impl) {
-            throw Exception("Could not resolve MoNA dependency: no MonaManager found");
-        }
-        auto mona_id = MonaManager(mona_manager_impl).getMonaInstance(spec);
-        if (!mona_id) {
-            throw Exception("Could not find MoNA instance with name \"{}\"",
-                            spec);
-        }
-        if (resolved) { *resolved = spec; }
-        return mona_id;
-
-    } else if (type == "ssg") { // SSG group
-
-        auto ssg_manager_impl = self->m_ssg_context.lock();
-        if (!ssg_manager_impl) {
-            throw Exception("Could not resolve SSG dependency: no SSGManager found");
-        }
-        auto group = SSGManager(ssg_manager_impl).getGroup(spec);
-        if (!group) {
-            throw Exception("Could not find SSG group with name \"{}\"", spec);
-        }
-        if (resolved) { *resolved = spec; }
-        return group;
-
-    } else if (kind == BEDROCK_KIND_CLIENT) {
-
-        auto client = findClient(type, spec);
-        if (client && resolved) { *resolved = client->getName(); }
-        return client;
-
-    } else if (kind == BEDROCK_KIND_PROVIDER) {
+    } else if (spec.find("@") == std::string::npos) { // local provider
 
         // the spec can be in the form "name" or "type:id"
         std::regex re(
@@ -144,7 +95,6 @@ std::shared_ptr<NamedDependency> DependencyFinder::find(
     } else { // Provider handle
 
         std::regex re(
-            "(?:([a-zA-Z_][a-zA-Z0-9_]*)\\->)?" // client name (name->)
             "([a-zA-Z_][a-zA-Z0-9_]*)"          // identifier (name or type)
             "(?::([0-9]+))?"                    // optional provider id
             "(?:@(.+))?");                      // optional locator (@address)
@@ -153,32 +103,59 @@ std::shared_ptr<NamedDependency> DependencyFinder::find(
             throw Exception("Ill-formated dependency specification \"{}\"", spec);
         }
 
-        auto client_name     = match.str(1); // client to use for provider handles
-        auto identifier      = match.str(2); // name or type
-        auto provider_id_str = match.str(3); // provider id
-        auto locator         = match.str(4); // address or "local" or MPI rank
+        auto identifier      = match.str(1); // name or type
+        auto provider_id_str = match.str(2); // provider id
+        auto locator         = match.str(3); // address or "local" or MPI rank
         if(locator.empty()) locator = "local";
 
         if (provider_id_str.empty()) {
-            // dependency specified as client->name@location
-            return makeProviderHandle(
-                client_name, type, identifier, locator, resolved);
+            // dependency specified as name@location
+            return makeProviderHandle(type, identifier, locator, resolved);
 
         } else {
-            // dependency specified as client->type:id@location
+            // dependency specified as type:id@location
             uint16_t provider_id = atoi(provider_id_str.c_str());
             if (type != identifier) {
                 throw Exception(
                         "Invalid provider type in \"{}\" (expected {})",
                         spec, type);
             }
-            return makeProviderHandle(
-                    client_name, type, provider_id, locator, resolved);
+            return makeProviderHandle(type, provider_id, locator, resolved);
         }
     }
     return nullptr;
 }
 
+std::shared_ptr<NamedDependency> DependencyFinder::find(
+        const std::string& type,
+        size_t index,
+        std::string* resolved) const {
+    spdlog::trace("DependencyFinder search dependency of type {} at index {}", type, index);
+
+    if (type == "pool") { // Argobots pool
+
+        auto pool = MargoManager(self->m_margo_context).getPool(index);
+        if (!pool) {
+            throw Exception("Could not find pool at index \"{}\"", index);
+        }
+        if (resolved) { *resolved = pool->getName(); }
+        return pool;
+
+    } else if (type == "xstream") { // Argobots xstream
+
+        auto xstream = MargoManager(self->m_margo_context).getXstream(index);
+        if (!xstream) {
+            throw Exception("Could not find xstream at index \"{}\"", index);
+        }
+        if (resolved) { *resolved = xstream->getName(); }
+        return xstream;
+
+    } else {
+        throw Exception("Only pools and xstream can be referenced by index");
+    }
+    return nullptr;
+}
+
 std::shared_ptr<NamedDependency>
 DependencyFinder::findProvider(const std::string& type,
                                uint16_t           provider_id) const {
@@ -216,43 +193,16 @@ DependencyFinder::findProvider(const std::string& type,
 }
 
 std::shared_ptr<NamedDependency>
-DependencyFinder::findClient(
-        const std::string& type,
-        const std::string& name) const {
-    auto client_manager_impl = self->m_client_manager.lock();
-    if (!client_manager_impl) {
-        throw Exception("Could not resolve client dependency: no ClientManager found");
-    }
-    if (!name.empty()) {
-        auto client = ClientManager(client_manager_impl).getClient(name);
-        if (!client) {
-            throw Exception("Could not find client named \"{}\"", name);
-        } else if (type != client->getType()) {
-            throw Exception(
-                "Invalid type {} for dependency \"{}\" (expected {})",
-                client->getType(), name, type);
-        }
-        return client;
-    } else {
-        auto client = ClientManager(client_manager_impl)
-            .getOrCreateAnonymous(type);
-        return client;
-    }
-}
-
-std::shared_ptr<NamedDependency>
-DependencyFinder::makeProviderHandle(const std::string& client_name,
-                                     const std::string& type,
+DependencyFinder::makeProviderHandle(const std::string& type,
                                      uint16_t           provider_id,
                                      const std::string& locatorArg,
                                      std::string* resolved) const {
     auto locator = locatorArg;
     spdlog::trace("Making provider handle of type {} with id {} and locator {}",
                   type, provider_id, locator);
-    auto        mid    = MargoManager(self->m_margo_context).getMargoInstance();
-    auto        client = findClient(type, client_name);
-    auto        service_factory = ModuleContext::getServiceFactory(type);
-    hg_addr_t   addr            = HG_ADDR_NULL;
+    auto engine = MargoManager(self->m_margo_context).getThalliumEngine();
+    thallium::endpoint endpoint;
+
     bool locator_is_number = true;
     int rank = 0;
     for(auto c : locator) {
@@ -284,89 +234,52 @@ DependencyFinder::makeProviderHandle(const std::string& client_name,
                 "Invalid type {} for provider handle to provider of type {}",
                 type, provider->getType());
         }
-        hg_return_t hret = margo_addr_self(mid, &addr);
-        if (hret != HG_SUCCESS) {
+        try {
+            endpoint = engine.self();
+        } catch(const std::exception& ex) {
             throw Exception(
-                "Failed to get self address (margo_addr_self returned {})",
-                std::to_string(hret));
+                    "Failed to get self address (engine.self() exception: {})",
+                    ex.what());
         }
 
     } else {
 
-        if (locator.rfind("ssg://", 0) == 0) {
-            auto ssg_manager_impl = self->m_ssg_context.lock();
-            if (!ssg_manager_impl) {
-                throw Exception(
-                    "Could not resolve SSG address: no SSGManager found");
-            }
-            hg_addr_t ssg_addr
-                = SSGManager(ssg_manager_impl).resolveAddress(locator);
-            hg_return_t hret = margo_addr_dup(mid, ssg_addr, &addr);
-            if (hret != HG_SUCCESS) {
-                throw Exception(
-                    "Failed to duplicate address returned by "
-                    "SSGManager (margo_addr_dup returned {})",
-                    std::to_string(hret));
-            }
-
-        } else {
-
-            hg_return_t hret = margo_addr_lookup(mid, locator.c_str(), &addr);
-            if (hret != HG_SUCCESS) {
-                throw Exception(
+        try {
+            endpoint = engine.lookup(locator);
+        } catch(const std::exception& ex) {
+            throw Exception(
                     "Failed to lookup address {} "
-                    "(margo_addr_lookup returned {})",
-                    locator, std::to_string(hret));
-            }
+                    "(engine.lookup() exception: {})",
+                    locator, ex.what());
         }
 
         ProviderDescriptor descriptor;
-        try {
-            auto spec = type + ":" + std::to_string(provider_id);
-            auto provider_manager_impl = self->m_provider_manager.lock();
-            if (!provider_manager_impl) {
-                throw Exception("Could not lookup provider: no ProviderManager found");
-            }
-            auto pid  = provider_manager_impl->get_provider_id();
-            self->lookupRemoteProvider(addr, pid, spec, &descriptor);
-        } catch (...) {
-            margo_addr_free(mid, addr);
-            throw;
+        auto spec = type + ":" + std::to_string(provider_id);
+        auto provider_manager_impl = self->m_provider_manager.lock();
+        if (!provider_manager_impl) {
+            throw Exception("Could not lookup provider: no ProviderManager found");
         }
+        auto pid  = provider_manager_impl->get_provider_id();
+        self->lookupRemoteProvider(endpoint, pid, spec, &descriptor);
     }
 
-    std::string name;
-    char        addr_str[256];
-    hg_size_t   addr_str_size = 256;
-    margo_addr_to_string(mid, addr_str, &addr_str_size, addr);
-    name = client->getName() + "->" + type + ":"
-         + std::to_string(provider_id) + "@" + addr_str;
+    auto name = type + ":" + std::to_string(provider_id) + "@" + static_cast<std::string>(endpoint);
 
     if (resolved) *resolved = name;
 
-    void* ph = service_factory->createProviderHandle(
-        client->getHandle<void*>(), addr, provider_id);
+    auto ph = ProviderHandle{endpoint, provider_id};
 
-    margo_addr_free(mid, addr);
-    return std::make_shared<NamedDependency>(
-        std::to_string(provider_id) + "@" + addr_str,
-        client->getType(), ph,
-        [service_factory](void* ph) {
-        service_factory->destroyProviderHandle(ph);
-    });
+    return std::make_shared<NamedDependency>(name, type, ph);
 }
 
 std::shared_ptr<NamedDependency>
-DependencyFinder::makeProviderHandle(const std::string& client_name,
-                                     const std::string& type,
+DependencyFinder::makeProviderHandle(const std::string& type,
                                      const std::string& name,
                                      const std::string& locatorArg,
                                      std::string* resolved) const {
     auto locator = locatorArg;
-    auto        mid    = self->m_margo_context->m_mid;
-    auto        client = findClient(type, client_name);
-    auto        service_factory = ModuleContext::getServiceFactory(type);
-    hg_addr_t   addr            = HG_ADDR_NULL;
+    auto engine = MargoManager(self->m_margo_context).getThalliumEngine();
+    thallium::endpoint endpoint;
     ProviderDescriptor descriptor;
     uint16_t provider_id;
     spdlog::trace("Making provider handle to provider {} of type {} at {}",
@@ -399,80 +312,47 @@ DependencyFinder::makeProviderHandle(const std::string& client_name,
                 "Invalid type {} for provider handle to provider of type {}",
                 type, provider->getType());
         }
-        hg_return_t hret = margo_addr_self(mid, &addr);
-        if (hret != HG_SUCCESS) {
-            throw Exception(
-                "Failed to get self address (margo_addr_self returned {})",
-                std::to_string(hret));
-        }
-        descriptor = ProviderDescriptor{
-            provider->getName(),
-            provider->getType(),
-            provider->getProviderID(),
-        };
-        provider_id = provider->getProviderID();
+        try {
+              endpoint = engine.self();
+          } catch(const std::exception& ex) {
+              throw Exception(
+                  "Failed to get self address (engine.self() exception: {})",
+                  ex.what());
+          }
+          descriptor = ProviderDescriptor{
+              provider->getName(),
+              provider->getType(),
+              provider->getProviderID()
+          };
+          provider_id = provider->getProviderID();
 
     } else {
 
-        if (locator.rfind("ssg://", 0) == 0) {
-            auto ssg_manager_impl = self->m_ssg_context.lock();
-            if (!ssg_manager_impl) {
-                throw Exception("Could not resolve SSG address: no SSGManager found");
-            }
-            hg_addr_t ssg_addr
-                = SSGManager(ssg_manager_impl).resolveAddress(locator);
-            hg_return_t hret = margo_addr_dup(mid, ssg_addr, &addr);
-            if (hret != HG_SUCCESS) {
-                throw Exception(
-                    "Failed to duplicate address returned by "
-                    "SSGManager (margo_addr_dup returned {})",
-                    std::to_string(hret));
-            }
-
-        } else {
-
-            hg_return_t hret = margo_addr_lookup(mid, locator.c_str(), &addr);
-            if (hret != HG_SUCCESS) {
-                throw Exception(
+        try {
+            endpoint = engine.lookup(locator);
+        } catch(const std::exception& ex) {
+            throw Exception(
                     "Failed to lookup address {} "
-                    "(margo_addr_lookup returned {})",
-                    locator, std::to_string(hret));
-            }
+                    "(engine.lookup() exception: {})",
+                    locator, ex.what());
         }
 
-        try {
-            auto provider_manager_impl = self->m_provider_manager.lock();
-            if (!provider_manager_impl) {
-                throw Exception("Could not get provider id: no ProviderManager found");
-            }
-            auto pid = provider_manager_impl->get_provider_id();
-            self->lookupRemoteProvider(addr, pid, name, &descriptor);
-        } catch (...) {
-            margo_addr_free(mid, addr);
-            throw;
+        auto provider_manager_impl = self->m_provider_manager.lock();
+        if (!provider_manager_impl) {
+            throw Exception("Could not get provider id: no ProviderManager found");
         }
+        auto pid = provider_manager_impl->get_provider_id();
+        self->lookupRemoteProvider(endpoint, pid, name, &descriptor);
         provider_id = descriptor.provider_id;
     }
 
-    std::string ph_name;
-    char        addr_str[256];
-    hg_size_t   addr_str_size = 256;
-    margo_addr_to_string(mid, addr_str, &addr_str_size, addr);
-    ph_name = client->getName() + "->" + type + ":"
-            + std::to_string(provider_id) + "@" + addr_str;
+    auto ph_name = type + ":" + std::to_string(provider_id) + "@" + static_cast<std::string>(endpoint);
 
     if (resolved) *resolved = ph_name;
 
-    void* ph = service_factory->createProviderHandle(
-        client->getHandle<void*>(), addr, descriptor.provider_id);
+    auto ph = ProviderHandle{endpoint, provider_id};
 
-    margo_addr_free(mid, addr);
-    return std::make_shared<NamedDependency>(
-        std::to_string(provider_id) + "@" + addr_str,
-        client->getType(), ph,
-        [service_factory](void* ph) {
-        service_factory->destroyProviderHandle(ph);
-    });
+    return std::make_shared<NamedDependency>(ph_name, type, ph);
 }
 
 } // namespace bedrock
diff --git a/src/DependencyFinderImpl.hpp b/src/DependencyFinderImpl.hpp
index dd3f088..3b99a8a 100644
--- a/src/DependencyFinderImpl.hpp
+++ b/src/DependencyFinderImpl.hpp
@@ -7,11 +7,7 @@
 #define __BEDROCK_DEPENDENCY_FINDER_IMPL_H
 
 #include "MargoManagerImpl.hpp"
-#include "ABTioManagerImpl.hpp"
-#include "MonaManagerImpl.hpp"
-#include "SSGManagerImpl.hpp"
 #include "ProviderManagerImpl.hpp"
-#include "ClientManagerImpl.hpp"
 #include "Formatting.hpp"
 #include "MPIEnvImpl.hpp"
 #include "bedrock/VoidPtr.hpp"
@@ -33,11 +29,7 @@ class DependencyFinderImpl {
     tl::engine                         m_engine;
     std::shared_ptr<MPIEnvImpl>        m_mpi;
     std::shared_ptr<MargoManagerImpl>  m_margo_context;
-    std::weak_ptr<ABTioManagerImpl>    m_abtio_context;
-    std::weak_ptr<SSGManagerImpl>      m_ssg_context;
-    std::weak_ptr<MonaManagerImpl>     m_mona_context;
     std::weak_ptr<ProviderManagerImpl> m_provider_manager;
-    std::weak_ptr<ClientManagerImpl>   m_client_manager;
     double                             m_timeout = 30.0;
 
     tl::remote_procedure m_lookup_provider;
@@ -52,11 +44,10 @@ class DependencyFinderImpl {
         spdlog::trace("DependencyFinderImpl destroyed");
     }
 
-    void lookupRemoteProvider(hg_addr_t addr, uint16_t provider_id,
+    void lookupRemoteProvider(const tl::endpoint& addr, uint16_t provider_id,
                               const std::string&  spec,
                               ProviderDescriptor* desc) {
-        auto ph = tl::provider_handle(tl::endpoint(m_engine, addr, false),
-                                      provider_id);
+        auto ph = tl::provider_handle(addr, provider_id);
         RequestResult<ProviderDescriptor> result
             = m_lookup_provider.on(ph)(spec, m_timeout);
         if (result.error() != "") throw Exception(result.error());
diff --git a/src/MargoManager.cpp b/src/MargoManager.cpp
index 1f399a0..7b9eebc 100644
--- a/src/MargoManager.cpp
+++ b/src/MargoManager.cpp
@@ -15,7 +15,6 @@ namespace bedrock {
 
 MargoManager::MargoManager(margo_instance_id mid)
 : self(std::make_shared<MargoManagerImpl>()) {
-    self->m_mid    = mid;
     self->m_engine = tl::engine(mid);
 }
 
@@ -26,32 +25,9 @@ MargoManager::MargoManager(const std::string& address,
     if (!configString.empty() && configString != "null") {
         args.json_config = configString.c_str();
     }
-    self->m_mid = margo_init_ext(address.c_str(), MARGO_SERVER_MODE, &args);
-    if (self->m_mid == MARGO_INSTANCE_NULL)
-        throw BEDROCK_DETAILED_EXCEPTION("Could not initialize Margo");
-    margo_enable_remote_shutdown(self->m_mid);
-    self->m_engine = tl::engine(self->m_mid);
-    setupMargoLoggingForInstance(self->m_mid);
-    // fill the m_pools array
-    size_t num_pools = margo_get_num_pools(self->m_mid);
-    for(unsigned i=0; i < num_pools; i++) {
-        margo_pool_info info;
-        if(HG_SUCCESS != margo_find_pool_by_index(self->m_mid, i, &info))
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Failed to retrieve pool information from Margo instance");
-        auto pool_entry = std::make_shared<PoolRef>(info.name, info.pool);
-        self->m_pools.emplace_back(pool_entry);
-    }
-    // fill the m_xstreams array
-    size_t num_es = margo_get_num_xstreams(self->m_mid);
-    for(unsigned i=0; i < num_es; i++) {
-        margo_xstream_info info;
-        if(HG_SUCCESS != margo_find_xstream_by_index(self->m_mid, i, &info))
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Failed to retrieve xstream information from Margo instance");
-        auto es_entry = std::make_shared<XstreamRef>(info.name, info.xstream);
-        self->m_xstreams.emplace_back(es_entry);
-    }
+    self->m_engine = tl::engine{address.c_str(), MARGO_SERVER_MODE, &args};
+    self->m_engine.enable_remote_shutdown();
+    setupMargoLoggingForInstance(self->m_engine.get_margo_instance());
 }
 
 // LCOV_EXCL_START
@@ -73,10 +49,10 @@ MargoManager::operator bool() const { return static_cast<bool>(self); }
 margo_instance_id MargoManager::getMargoInstance() const {
     if(!self) return MARGO_INSTANCE_NULL;
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    return self->m_mid;
+    return self->m_engine.get_margo_instance();
 }
 
-tl::engine MargoManager::getThalliumEngine() const {
+const tl::engine& MargoManager::getThalliumEngine() const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
     return self->m_engine;
 }
@@ -88,244 +64,166 @@ std::string MargoManager::getCurrentConfig() const {
 
 std::shared_ptr<NamedDependency> MargoManager::getDefaultHandlerPool() const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    ABT_pool p;
-    int      ret = margo_get_handler_pool(self->m_mid, &p);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not get handler pool from Margo instance");
+    try {
+        auto pool = self->m_engine.get_handler_pool();
+        auto name = self->m_engine.pools()[pool].name();
+        return std::make_shared<PoolRef>(self->m_engine, name, pool);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_pools.begin(), self->m_pools.end(),
-        [p](std::shared_ptr<PoolRef>& entry) {
-            return entry->getHandle<ABT_pool>() == p;
-        });
-    return *it;
 }
 
 std::shared_ptr<NamedDependency> MargoManager::getPool(const std::string& name) const {
-    auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_pool_info info = {ABT_POOL_NULL,"",0};
-    hg_return_t ret = margo_find_pool_by_name(self->m_mid, name.c_str(), &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool \"{}\" from Margo instance",
-            name);
+    try {
+        auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
+        auto pool = self->m_engine.pools()[name];
+        return std::make_shared<PoolRef>(self->m_engine, pool.name(), pool);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_pools.begin(), self->m_pools.end(),
-        [&info](std::shared_ptr<PoolRef>& entry) {
-            return entry->getHandle<ABT_pool>() == info.pool;
-        });
-    return *it;
 }
 
 std::shared_ptr<NamedDependency> MargoManager::getPool(uint32_t index) const {
-    auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_pool_info info = {ABT_POOL_NULL,"",0};
-    hg_return_t ret = margo_find_pool_by_index(self->m_mid, index, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool at index {} from Margo instance",
-            index);
+    try {
+        auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
+        auto pool = self->m_engine.pools()[index];
+        return std::make_shared<PoolRef>(self->m_engine, pool.name(), pool);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_pools.begin(), self->m_pools.end(),
-        [&info](std::shared_ptr<PoolRef>& entry) {
-            return entry->getHandle<ABT_pool>() == info.pool;
-        });
-    return *it;
 }
 
-std::shared_ptr<NamedDependency> MargoManager::getPool(ABT_pool pool) const {
-    auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_pool_info info = {ABT_POOL_NULL,"",0};
-    hg_return_t ret = margo_find_pool_by_handle(self->m_mid, pool, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool from its ABT_pool handle in Margo instance");
+std::shared_ptr<NamedDependency> MargoManager::getPool(ABT_pool abt_pool) const {
+    try {
+        auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
+        auto pool = self->m_engine.pools()[tl::pool{abt_pool}];
+        return std::make_shared<PoolRef>(self->m_engine, pool.name(), pool);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_pools.begin(), self->m_pools.end(),
-        [&info](std::shared_ptr<PoolRef>& entry) {
-            return entry->getHandle<ABT_pool>() == info.pool;
-        });
-    return *it;
 }
 
 size_t MargoManager::getNumPools() const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    return margo_get_num_pools(self->m_mid);
+    return self->m_engine.pools().size();
 }
 
 std::shared_ptr<NamedDependency> MargoManager::addPool(const std::string& config) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
+    auto mid = self->m_engine.get_margo_instance();
     margo_pool_info info;
-    hg_return_t ret = margo_add_pool_from_json(
-        self->m_mid, config.c_str(), &info);
+    hg_return_t ret = margo_add_pool_from_json(mid, config.c_str(), &info);
     if (ret != HG_SUCCESS) {
         throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not add pool to Margo instance");
+                "Could not add pool to Margo instance");
     }
-    auto pool_entry = std::make_shared<PoolRef>(info.name, info.pool);
-    self->m_pools.push_back(pool_entry);
-    return pool_entry;
+    return std::make_shared<PoolRef>(self->m_engine, info.name, tl::pool{info.pool});
 }
 
 void MargoManager::removePool(uint32_t index) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_pool_info info;
-    hg_return_t ret = margo_find_pool_by_index(self->m_mid, index, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool at index {} from Margo instance",
-            index);
+    try {
+        self->m_engine.pools().remove(index);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    guard.unlock();
-    removePool(info.pool);
 }
 
 void MargoManager::removePool(const std::string& name) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    auto it = std::find_if(self->m_pools.begin(), self->m_pools.end(),
-        [&name](auto& p) { return p->getName() == name; });
-    if(it == self->m_pools.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool named \"{}\" known to Bedrock", name);
-    }
-    if(it->use_count() != 1) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Pool \"{}\" is still in use by some dependencies", name);
-    }
-    hg_return_t ret = margo_remove_pool_by_name(self->m_mid, name.c_str());
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not remove pool \"{}\" from Margo instance", name);
+    try {
+        self->m_engine.pools().remove(name);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    self->m_pools.erase(it);
 }
 
 void MargoManager::removePool(ABT_pool pool) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_pool_info info;
-    hg_return_t ret = margo_find_pool_by_handle(self->m_mid, pool, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find pool from its ABT_pool handle in Margo instance");
+    try {
+        self->m_engine.pools().remove(tl::pool{pool});
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    guard.unlock();
-    removePool(info.name);
 }
 
 std::shared_ptr<NamedDependency> MargoManager::getXstream(const std::string& name) const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_xstream_info info = {ABT_XSTREAM_NULL,"",0};
-    hg_return_t ret = margo_find_xstream_by_name(self->m_mid, name.c_str(), &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream \"{}\" from Margo instance",
-            name);
+    try {
+        auto es = self->m_engine.xstreams()[name];
+        return std::make_shared<XstreamRef>(self->m_engine, es.name(), es);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_xstreams.begin(), self->m_xstreams.end(),
-        [&info](std::shared_ptr<XstreamRef>& entry) {
-            return entry->getHandle<ABT_xstream>() == info.xstream;
-        });
-    return *it;
 }
 
 std::shared_ptr<NamedDependency> MargoManager::getXstream(uint32_t index) const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_xstream_info info = {ABT_XSTREAM_NULL,"",0};
-    hg_return_t ret = margo_find_xstream_by_index(self->m_mid, index, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream at index {} from Margo instance",
-            index);
+    try {
+        auto es = self->m_engine.xstreams()[index];
+        return std::make_shared<XstreamRef>(self->m_engine, es.name(), es);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_xstreams.begin(), self->m_xstreams.end(),
-        [&info](std::shared_ptr<XstreamRef>& entry) {
-            return entry->getHandle<ABT_xstream>() == info.xstream;
-        });
-    return *it;
 }
 
-std::shared_ptr<NamedDependency> MargoManager::getXstream(ABT_xstream xstream) const {
+std::shared_ptr<NamedDependency> MargoManager::getXstream(ABT_xstream abt_es) const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_xstream_info info = {ABT_XSTREAM_NULL,"",0};
-    hg_return_t ret = margo_find_xstream_by_handle(self->m_mid, xstream, &info);
-    if (ret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream from its ABT_xstream handle in Margo instance");
+    try {
+        auto es = self->m_engine.xstreams()[tl::xstream{abt_es}];
+        return std::make_shared<XstreamRef>(self->m_engine, es.name(), es);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    auto it = std::find_if(self->m_xstreams.begin(), self->m_xstreams.end(),
-        [&info](std::shared_ptr<XstreamRef>& entry) {
-            return entry->getHandle<ABT_xstream>() == info.xstream;
-        });
-    return *it;
 }
 
 size_t MargoManager::getNumXstreams() const {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    return margo_get_num_xstreams(self->m_mid);
+    try {
+        return self->m_engine.xstreams().size();
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
+    }
 }
 
 std::shared_ptr<NamedDependency> MargoManager::addXstream(const std::string& config) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
+    auto mid = self->m_engine.get_margo_instance();
     margo_xstream_info info;
-    hg_return_t hret = margo_add_xstream_from_json(
-        self->m_mid, config.c_str(), &info);
-    if (hret != HG_SUCCESS) {
+    hg_return_t ret = margo_add_xstream_from_json(mid, config.c_str(), &info);
+    if (ret != HG_SUCCESS) {
         throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not add xstream to Margo instance (margo_add_xstream_from_json returned {})",
-            std::to_string(hret));
+                "Could not add xstream to Margo instance");
     }
-    auto entry = std::make_shared<XstreamRef>(info.name, info.xstream);
-    self->m_xstreams.push_back(entry);
-    return entry;
+    return std::make_shared<XstreamRef>(self->m_engine, info.name, tl::xstream{info.xstream});
 }
 
 void MargoManager::removeXstream(uint32_t index) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_xstream_info info;
-    hg_return_t hret = margo_find_xstream_by_index(self->m_mid, index, &info);
-    if (hret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream at index {} from Margo instance "
-            "(margo_find_xstream_by_index returned {})",
-            index, std::to_string(hret));
+    try {
+        self->m_engine.xstreams().remove(index);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    guard.unlock();
-    removeXstream(info.xstream);
 }
 
 void MargoManager::removeXstream(const std::string& name) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    auto it = std::find_if(self->m_xstreams.begin(), self->m_xstreams.end(),
-        [&name](auto& es) { return es->getName() == name; });
-    if(it == self->m_xstreams.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream named \"{}\" known to Bedrock", name);
-    }
-    if(it->use_count() != 1) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Xstream \"{}\" is still in use by some dependencies", name);
+    try {
+        self->m_engine.xstreams().remove(name);
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    hg_return_t hret = margo_remove_xstream_by_name(self->m_mid, name.c_str());
-    if (hret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not remove xstream \"{}\" from Margo instance "
-            "(margo_remove_xstream_by_name returned {})",
-            name, std::to_string(hret));
-    }
-    self->m_xstreams.erase(it);
 }
 
-void MargoManager::removeXstream(ABT_xstream xstream) {
+void MargoManager::removeXstream(ABT_xstream es) {
     auto guard = std::unique_lock<tl::mutex>(self->m_mtx);
-    margo_xstream_info info;
-    hg_return_t hret = margo_find_xstream_by_handle(self->m_mid, xstream, &info);
-    if (hret != HG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find xstream from its ABT_xstream handle in Margo instance "
-            "(margo_find_xstream_by_handle returned {})", std::to_string(hret));
+    try {
+        self->m_engine.xstreams().remove(tl::xstream{es});
+    } catch(const tl::exception& ex) {
+        throw Exception{"{}", ex.what()};
     }
-    guard.unlock();
-    removeXstream(info.name);
 }
 
+
 } // namespace bedrock
diff --git a/src/MargoManagerImpl.hpp b/src/MargoManagerImpl.hpp
index 8cc686f..69f3905 100644
--- a/src/MargoManagerImpl.hpp
+++ b/src/MargoManagerImpl.hpp
@@ -20,39 +20,49 @@ using nlohmann::json;
 
 struct PoolRef : public NamedDependency {
 
-    PoolRef(std::string name, ABT_pool pool)
-    : NamedDependency(
-        std::move(name),
-        "pool", pool,
-        std::function<void(void*)>()) {}
+    tl::engine engine;
 
+    PoolRef(tl::engine e, std::string name, tl::pool pool)
+    : NamedDependency(std::move(name), "pool", std::move(pool))
+    , engine{std::move(e)} {
+        engine.pools().ref_incr(getHandle<tl::pool>());
+    }
+
+    PoolRef(const PoolRef&) = delete;
+    PoolRef(PoolRef&&) = delete;
+
+    ~PoolRef() {
+        engine.pools().release(getHandle<tl::pool>());
+    }
 };
 
 struct XstreamRef : public NamedDependency {
 
-    XstreamRef(std::string name, ABT_xstream xstream)
-    : NamedDependency(
-        std::move(name),
-        "xstream", xstream,
-        std::function<void(void*)>()) {}
+    tl::engine engine;
 
+    XstreamRef(tl::engine e, std::string name, tl::xstream es)
+    : NamedDependency(std::move(name), "xstream", std::move(es))
+    , engine{std::move(e)} {
+        engine.xstreams().ref_incr(getHandle<tl::xstream>());
+    }
+
+    XstreamRef(const XstreamRef&) = delete;
+    XstreamRef(XstreamRef&&) = delete;
+
+    ~XstreamRef() {
+        engine.xstreams().release(getHandle<tl::xstream>());
+    }
 };
 
 class MargoManagerImpl {
 
   public:
-    tl::mutex         m_mtx;
-    margo_instance_id m_mid;
-    tl::engine        m_engine;
-
-    // to keep track of who is using which pool and xstream,
-    // we keep a shared_ptr to a PoolRef/XstreamRef with just
-    // the name of the pool/xstream and its handle.
-    std::vector<std::shared_ptr<XstreamRef>> m_xstreams;
-    std::vector<std::shared_ptr<PoolRef>> m_pools;
+    tl::mutex  m_mtx;
+    tl::engine m_engine;
 
     json makeConfig() const {
-        char* str    = margo_get_config_opt(m_mid, MARGO_CONFIG_USE_NAMES);
+        auto mid = m_engine.get_margo_instance();
+        char* str    = margo_get_config_opt(mid, MARGO_CONFIG_USE_NAMES);
         auto  config = json::parse(str);
         free(str);
         return config;
diff --git a/src/MonaManager.cpp b/src/MonaManager.cpp
deleted file mode 100644
index c1a34c1..0000000
--- a/src/MonaManager.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * (C) 2022 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#include <bedrock/MonaManager.hpp>
-#include <bedrock/MargoManager.hpp>
-#include <bedrock/DetailedException.hpp>
-#include "MonaManagerImpl.hpp"
-#include "JsonUtil.hpp"
-#include <margo.h>
-
-namespace tl = thallium;
-
-namespace bedrock {
-
-using nlohmann::json;
-
-MonaManager::MonaManager(const MargoManager& margoCtx,
-                         const Jx9Manager& jx9,
-                         const json& config,
-                         const std::string& defaultAddress)
-: self(std::make_shared<MonaManagerImpl>()) {
-    self->m_default_address = defaultAddress;
-    self->m_margo_manager   = margoCtx;
-    self->m_jx9_manager     = jx9;
-    if (config.is_null()) return;
-#ifndef ENABLE_MONA
-    (void)defaultAddress;
-    if (!(config.is_array() && config.empty()))
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Configuration has \"mona\" entry but Bedrock wasn't compiled with MoNA support");
-#else
-    if (!config.is_array()) {
-        throw BEDROCK_DETAILED_EXCEPTION("\"mona\" field should be an array");
-    }
-    for(auto& description : config) {
-        addMonaInstanceFromJSON(description);
-    }
-#endif
-}
-
-// LCOV_EXCL_START
-
-MonaManager::MonaManager(const MonaManager&) = default;
-
-MonaManager::MonaManager(MonaManager&&) = default;
-
-MonaManager& MonaManager::operator=(const MonaManager&) = default;
-
-MonaManager& MonaManager::operator=(MonaManager&&) = default;
-
-MonaManager::~MonaManager() = default;
-
-MonaManager::operator bool() const { return static_cast<bool>(self); }
-
-// LCOV_EXCL_STOP
-
-std::shared_ptr<NamedDependency>
-MonaManager::getMonaInstance(const std::string& name) const {
-#ifndef ENABLE_MONA
-    (void)name;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiled with MoNA support");
-#else
-    auto guard = std::unique_lock<tl::mutex>{self->m_mtx};
-    auto it = std::find_if(self->m_instances.begin(), self->m_instances.end(),
-                           [&name](const auto& p) { return p->getName() == name; });
-    if (it == self->m_instances.end())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find MoNA instance named \"{}\"", name);
-    return *it;
-#endif
-}
-
-std::shared_ptr<NamedDependency> MonaManager::getMonaInstance(size_t index) const {
-#ifndef ENABLE_MONA
-    (void)index;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiled with MoNA support");
-#else
-    auto guard = std::unique_lock<tl::mutex>{self->m_mtx};
-    if (index >= self->m_instances.size())
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find MoNA instance at index {}", index);
-    return self->m_instances[index];
-#endif
-}
-
-
-size_t MonaManager::numMonaInstances() const {
-#ifndef ENABLE_MONA
-    return 0;
-#else
-    auto guard = std::unique_lock<tl::mutex>{self->m_mtx};
-    return self->m_instances.size();
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-MonaManager::addMonaInstance(const std::string& name,
-                             std::shared_ptr<NamedDependency> pool,
-                             const std::string& address) {
-#ifndef ENABLE_MONA
-    (void)name;
-    (void)pool;
-    (void)address;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock wasn't compiled with MoNA support");
-#else
-    auto guard = std::unique_lock<tl::mutex>{self->m_mtx};
-    // check if the name doesn't already exist
-    auto it = std::find_if(
-        self->m_instances.begin(), self->m_instances.end(),
-        [&name](const auto& instance) { return instance->getName() == name; });
-    if (it != self->m_instances.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION("Mona instance name \"{}\" already used", name);
-    }
-    // all good, can instanciate
-    mona_instance_t mona = mona_init_pool(
-        address.c_str(), true, nullptr, pool->getHandle<ABT_pool>());
-
-    if (!mona) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not initialize mona instance");
-    }
-    auto entry = std::make_shared<MonaEntry>(name, mona, pool);
-    self->m_instances.push_back(entry);
-    return entry;
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-MonaManager::addMonaInstanceFromJSON(const json& description) {
-    static const json configSchema = R"(
-    {
-        "$schema": "https://json-schema.org/draft/2019-09/schema",
-        "type": "object",
-        "properties": {
-            "name": {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-            "pool": {"oneOf": [
-                {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-                {"type": "integer", "minimum": 0 }
-            ]},
-            "address": {"type": "string"}
-        },
-        "required": ["name"]
-    }
-    )"_json;
-    static const JsonValidator validator{configSchema};
-    validator.validate(description, "MonaManager");
-    auto name = description["name"].get<std::string>();
-    auto address = description.value("address", self->m_default_address);
-    std::shared_ptr<NamedDependency> pool;
-    // find pool
-    if(description.contains("pool") && description["pool"].is_number()) {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description["pool"].get<uint32_t>());
-    } else {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description.value("pool", "__primary__"));
-    }
-    return addMonaInstance(name, pool, address);
-}
-
-json MonaManager::getCurrentConfig() const {
-#ifndef ENABLE_MONA
-    return json::array();
-#else
-    auto guard = std::unique_lock<tl::mutex>{self->m_mtx};
-    return self->makeConfig();
-#endif
-}
-
-} // namespace bedrock
diff --git a/src/MonaManagerImpl.hpp b/src/MonaManagerImpl.hpp
deleted file mode 100644
index c0d37b2..0000000
--- a/src/MonaManagerImpl.hpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * (C) 2022 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_MONA_CONTEXT_IMPL_H
-#define __BEDROCK_MONA_CONTEXT_IMPL_H
-
-#include "bedrock/MargoManager.hpp"
-#include "bedrock/MonaManager.hpp"
-#include "bedrock/Jx9Manager.hpp"
-#include "MargoManagerImpl.hpp"
-#include <nlohmann/json.hpp>
-#include <spdlog/spdlog.h>
-#ifdef ENABLE_MONA
-#include <mona.h>
-#endif
-#include <string>
-#include <vector>
-#include <unordered_map>
-
-namespace bedrock {
-
-using nlohmann::json;
-
-class MonaEntry : public NamedDependency {
-
-    public:
-
-#ifdef ENABLE_MONA
-    std::shared_ptr<NamedDependency> pool;
-
-    MonaEntry(const MonaEntry&) = delete;
-    MonaEntry(MonaEntry&&) = delete;
-
-    template<typename S>
-    MonaEntry(S&& name, mona_instance_t mona, std::shared_ptr<NamedDependency> p)
-    : NamedDependency(std::move(name), "mona", mona, releaseMonaInstance)
-    , pool(std::move(p)) {}
-
-    static void releaseMonaInstance(void* args) {
-        auto mona_id = static_cast<mona_instance_t>(args);
-        mona_finalize(mona_id);
-    }
-#endif
-
-    json makeConfig() const {
-        json config      = json::object();
-#ifdef ENABLE_MONA
-        config["name"]   = getName();
-        config["pool"]   = pool->getName();
-        config["config"] = json::object();
-        auto mona = getHandle<mona_instance_t>();
-        mona_addr_t self_addr;
-        na_return_t ret = mona_addr_self(mona, &self_addr);
-        if(ret != NA_SUCCESS) {
-            config["address"] = nullptr;
-        } else {
-            char self_addr_str[256];
-            size_t self_addr_size = 256;
-            ret = mona_addr_to_string(mona, self_addr_str, &self_addr_size, self_addr);
-            if(ret != NA_SUCCESS)
-                config["address"] = nullptr;
-            else
-                config["address"] = std::string{self_addr_str};
-        }
-#endif
-        return config;
-    }
-
-    ~MonaEntry() {
-        spdlog::trace("Finalizing MoNA instance {}", getName());
-    }
-};
-
-class MonaManagerImpl {
-
-    friend class MonaManager;
-
-  public:
-    std::string                             m_default_address;
-    std::shared_ptr<MargoManagerImpl>       m_margo_manager;
-    std::shared_ptr<Jx9ManagerImpl>         m_jx9_manager;
-    std::vector<std::shared_ptr<MonaEntry>> m_instances;
-    tl::mutex                               m_mtx;
-
-    json makeConfig() const {
-        json config = json::array();
-        for (auto& i : m_instances) { config.push_back(i->makeConfig()); }
-        return config;
-    }
-
-    ~MonaManagerImpl() {
-        spdlog::trace("Finalizing MoNA");
-    }
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/src/ProviderManager.cpp b/src/ProviderManager.cpp
index 175b9ae..98801ac 100644
--- a/src/ProviderManager.cpp
+++ b/src/ProviderManager.cpp
@@ -4,8 +4,8 @@
  * See COPYRIGHT in top-level directory.
  */
 #include <bedrock/ProviderManager.hpp>
-#include <bedrock/ModuleContext.hpp>
-#include <bedrock/AbstractServiceFactory.hpp>
+#include <bedrock/ModuleManager.hpp>
+#include <bedrock/AbstractComponent.hpp>
 #include <bedrock/DependencyFinder.hpp>
 #include <bedrock/DetailedException.hpp>
 
@@ -30,7 +30,7 @@ ProviderManager::ProviderManager(const MargoManager& margo,
                                  uint16_t provider_id,
                                  std::shared_ptr<NamedDependency> pool)
 : self(std::make_shared<ProviderManagerImpl>(margo.getThalliumEngine(),
-                                             provider_id, tl::pool(pool->getHandle<ABT_pool>()))) {
+                                             provider_id, tl::pool(pool->getHandle<tl::pool>()))) {
     self->m_margo_manager = margo;
     self->m_jx9_manager   = jx9;
 }
@@ -86,65 +86,6 @@ std::shared_ptr<ProviderDependency> ProviderManager::getProvider(size_t index) c
     return self->m_providers[index];
 }
 
-std::shared_ptr<ProviderDependency>
-ProviderManager::registerProvider(
-    const std::string& name, const std::string& type, uint16_t provider_id,
-    std::shared_ptr<NamedDependency> pool, const json& config,
-    const ResolvedDependencyMap& dependencies,
-    const std::vector<std::string>& tags) {
-
-    std::shared_ptr<LocalProvider> entry;
-    auto service_factory = ModuleContext::getServiceFactory(type);
-    if (!service_factory) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find service factory for provider type \"{}\"", type);
-    }
-
-    {
-        std::lock_guard<tl::mutex> lock(self->m_providers_mtx);
-        auto                       it = self->resolveSpec(name);
-        if (it != self->m_providers.end()) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Name \"{}\" already used by another provider", name);
-        }
-
-        if (provider_id == std::numeric_limits<uint16_t>::max())
-            provider_id = self->getAvailableProviderID();
-
-        it = std::find_if(self->m_providers.begin(), self->m_providers.end(),
-                [provider_id](const auto& p) { return p->getProviderID() == provider_id; });
-        if (it != self->m_providers.end()) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Another provider already uses provider ID {}", provider_id);
-        }
-
-        auto margo = MargoManager(self->m_margo_manager);
-
-        FactoryArgs args;
-        args.name         = name;
-        args.mid          = margo.getMargoInstance();
-        args.engine       = margo.getThalliumEngine();
-        args.pool         = pool->getHandle<ABT_pool>();
-        args.config       = config.is_null() ? std::string{"{}"} : config.dump();
-        args.tags         = tags;
-        args.dependencies = dependencies;
-        args.provider_id  = provider_id;
-
-        auto handle = service_factory->registerProvider(args);
-
-        entry = std::make_shared<LocalProvider>(
-            name, type, provider_id, handle, service_factory, pool,
-            std::move(dependencies), tags);
-
-        spdlog::trace("Registered provider {} of type {} with provider id {}",
-                      name, type, provider_id);
-
-        self->m_providers.push_back(entry);
-    }
-    self->m_providers_cv.notify_all();
-    return entry;
-}
-
 void ProviderManager::deregisterProvider(const std::string& spec) {
     std::lock_guard<tl::mutex> lock(self->m_providers_mtx);
     auto                       it = self->resolveSpec(spec);
@@ -161,17 +102,12 @@ ProviderManager::addProviderFromJSON(const json& description) {
         throw BEDROCK_DETAILED_EXCEPTION("No DependencyFinder set in ProviderManager");
     }
     auto dependencyFinder = DependencyFinder(self->m_dependency_finder);
-
     static const json configSchema = R"(
     {
         "$schema": "https://json-schema.org/draft/2019-09/schema",
         "type": "object",
         "properties": {
             "name": {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-            "pool": {"oneOf": [
-                {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-                {"type": "integer", "minimum": 0 }
-            ]},
             "provider_id": {"type": "integer", "minimum": 0, "maximum": 65535},
             "type": {"type": "string"},
             "tags": {
@@ -183,7 +119,15 @@ ProviderManager::addProviderFromJSON(const json& description) {
                 "additionalProperties": {
                     "anyOf": [
                         {"type": "string"},
-                        {"type": "array", "items": {"type": "string"}}
+                        {"type": "integer"},
+                        {"type": "array",
+                         "items": {
+                            "anyOf": [
+                                {"type": "string"},
+                                {"type": "integer"}
+                            ]
+                          }
+                        }
                     ]
                 }
             },
@@ -195,85 +139,93 @@ ProviderManager::addProviderFromJSON(const json& description) {
     static const JsonValidator validator{configSchema};
     validator.validate(description, "ProviderManager");
 
-    auto& name = description["name"].get_ref<const std::string&>();
     auto& type = description["type"].get_ref<const std::string&>();
-    auto provider_id = description.value("provider_id", std::numeric_limits<uint16_t>::max());
-
-    auto service_factory = ModuleContext::getServiceFactory(type);
-    if (!service_factory) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Could not find service factory for provider type \"{}\"", type);
-    }
-
-    auto config = description.value("config", json::object());
-    auto configStr = config.dump();
-
-    // find pool
-    std::shared_ptr<NamedDependency> pool;
-    if(description.contains("pool") && description["pool"].is_number()) {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description["pool"].get<uint32_t>());
-    } else {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description.value("pool", "__primary__"));
-    }
-
-    std::vector<std::string> tags;
-    for(auto& tag : description.value("tags", json::array())) {
-        tags.push_back(tag.get<std::string>());
-    }
+    ComponentArgs args;
+    args.name = description["name"];
+    args.provider_id = description.value("provider_id", std::numeric_limits<uint16_t>::max());
+    args.config = description.value("config", json::object()).dump();
+    args.engine = MargoManager{self->m_margo_manager}.getThalliumEngine();
+    for(auto& tag : description.value("tags", json::array()))
+        args.tags.push_back(tag.get<std::string>());
 
     auto deps_from_config = description.value("dependencies", json::object());
-    ResolvedDependencyMap resolved_dependency_map;
+    auto requested_dependencies = ModuleManager::getDependencies(type, args);
+    auto& resolved_dependency_map = args.dependencies;
 
-    for (const auto& dependency : service_factory->getProviderDependencies(configStr.c_str())) {
+    for (const auto& dependency : requested_dependencies) {
         spdlog::trace("Resolving dependency {}", dependency.name);
         if (deps_from_config.contains(dependency.name)) {
             auto dep_config = deps_from_config[dependency.name];
-            if (!(dependency.flags & BEDROCK_ARRAY)) { // dependency should be a string
+            if (!(dependency.is_array)) { // dependency should be a string or an integer
                 if (dep_config.is_array() && dep_config.size() == 1) {
                     // an array of length 1 can be converted into a single string
                     dep_config = dep_config[0];
                 }
-                if (!dep_config.is_string()) {
-                    throw BEDROCK_DETAILED_EXCEPTION("Dependency \"{}\" should be a string",
-                            dependency.name);
-                }
-                auto dep_handle = dependencyFinder.find(
-                        dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
-                        dep_config.get<std::string>(), nullptr);
-                resolved_dependency_map[dependency.name].is_array = false;
-                resolved_dependency_map[dependency.name].dependencies.push_back(dep_handle);
+                auto dep_handle = dep_config.is_string() ?
+                    dependencyFinder.find(dependency.type, dep_config.get<std::string>(), nullptr)
+                  : dependencyFinder.find(dependency.type, dep_config.get<size_t>(), nullptr);
+                resolved_dependency_map[dependency.name].push_back(dep_handle);
 
             } else { // dependency is an array
-                if (dep_config.is_string()) {
+
+                if (dep_config.is_string() || dep_config.is_number_unsigned()) {
                     // a single string can be converted into an array of size 1
                     auto tmp_array = json::array();
                     tmp_array.push_back(dep_config);
                     dep_config = tmp_array;
                 }
                 if (!dep_config.is_array()) {
-                    throw BEDROCK_DETAILED_EXCEPTION("Dependency \"{}\" should be an array",
+                    throw BEDROCK_DETAILED_EXCEPTION(
+                            "Dependency \"{}\" should be an array",
                             dependency.name);
                 }
-                std::vector<std::string> deps;
                 for (const auto& elem : dep_config) {
-                    auto dep_handle = dependencyFinder.find(
-                            dependency.type, BEDROCK_GET_KIND_FROM_FLAG(dependency.flags),
-                            elem.get<std::string>(), nullptr);
-                    resolved_dependency_map[dependency.name].is_array = true;
-                    resolved_dependency_map[dependency.name].dependencies.push_back(dep_handle);
+                    auto dep_handle = elem.is_string() ?
+                        dependencyFinder.find(dependency.type, elem.get<std::string>(), nullptr)
+                      : dependencyFinder.find(dependency.type, elem.get<size_t>(), nullptr);
+
+                    resolved_dependency_map[dependency.name].push_back(dep_handle);
                 }
             }
-        } else if (dependency.flags & BEDROCK_REQUIRED) {
+        } else if (dependency.is_required) {
             throw BEDROCK_DETAILED_EXCEPTION(
-                "Missing dependency \"{}\" of type \"{}\" in provider configuration",
-                dependency.name, dependency.type);
+                    "Missing dependency \"{}\" of type \"{}\" in provider configuration",
+                    dependency.name, dependency.type);
         }
     }
 
-    return registerProvider(name, type, provider_id, pool, config,
-                            resolved_dependency_map, tags);
+    std::shared_ptr<LocalProvider> entry;
+    {
+        std::unique_lock<tl::mutex> lock(self->m_providers_mtx);
+        auto                        it = self->resolveSpec(args.name);
+        if (it != self->m_providers.end()) {
+            throw BEDROCK_DETAILED_EXCEPTION(
+                    "Name \"{}\" already used by another provider", args.name);
+        }
+
+        if (args.provider_id == std::numeric_limits<uint16_t>::max())
+            args.provider_id = self->getAvailableProviderID();
+
+        it = std::find_if(self->m_providers.begin(), self->m_providers.end(),
+                [&](const auto& p) { return p->getProviderID() == args.provider_id; });
+        if (it != self->m_providers.end()) {
+            throw BEDROCK_DETAILED_EXCEPTION(
+                    "Another provider already uses provider ID {}", args.provider_id);
+        }
+
+        auto handle = ModuleManager::createComponent(type, args);
+
+        entry = std::make_shared<LocalProvider>(
+                args.name, type, args.provider_id, handle,
+                requested_dependencies, args.dependencies, args.tags);
+
+        spdlog::trace("Registered provider {} of type {} with provider id {}",
+                args.name, type, args.provider_id);
+
+        self->m_providers.push_back(entry);
+    }
+    self->m_providers_cv.notify_all();
+    return entry;
 }
 
 void ProviderManager::addProviderListFromJSON(const json& list) {
@@ -288,24 +240,6 @@ void ProviderManager::addProviderListFromJSON(const json& list) {
     }
 }
 
-void ProviderManager::changeProviderPool(const std::string& provider,
-                                         const std::string& pool_name) {
-    // find the provider
-    std::lock_guard<tl::mutex> lock(self->m_providers_mtx);
-    auto                       it = self->resolveSpec(provider);
-    if (it == self->m_providers.end())
-        throw BEDROCK_DETAILED_EXCEPTION("Provider with spec \"{}\" not found", provider);
-    // find the pool
-    auto margo_manager = MargoManager(self->m_margo_manager);
-    auto pool = margo_manager.getPool(pool_name);
-    if(!pool) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find pool named \"{}\"", pool_name);
-    }
-    // call the provider's change_pool callback
-    (*it)->factory->changeProviderPool((*it)->getHandle<void*>(), pool->getHandle<ABT_pool>());
-    (*it)->pool = pool;
-}
-
 void ProviderManager::migrateProvider(
         const std::string& provider,
         const std::string& dest_addr,
@@ -317,10 +251,9 @@ void ProviderManager::migrateProvider(
     auto                       it = self->resolveSpec(provider);
     if (it == self->m_providers.end())
         throw BEDROCK_DETAILED_EXCEPTION("Provider with spec \"{}\" not found", provider);
-    // call the provider's migrateProvider callback
+    ComponentPtr theProvider = (*it)->getHandle<ComponentPtr>();
     try {
-        (*it)->factory->migrateProvider(
-                (*it)->getHandle<void*>(),
+        theProvider->migrate(
                 dest_addr.c_str(), dest_provider_id,
                 migration_config.c_str(),
                 remove_source);
@@ -339,13 +272,12 @@ void ProviderManager::snapshotProvider(
     auto                       it = self->resolveSpec(provider);
     if (it == self->m_providers.end())
         throw BEDROCK_DETAILED_EXCEPTION("Provider with spec \"{}\" not found", provider);
-    // call the provider's snapshotProvider callback
+    ComponentPtr theProvider = (*it)->getHandle<ComponentPtr>();
     try {
-        (*it)->factory->snapshotProvider(
-                (*it)->getHandle<void*>(),
-                dest_path.c_str(),
-                snapshot_config.c_str(),
-                remove_source);
+        theProvider->snapshot(
+            dest_path.c_str(),
+            snapshot_config.c_str(),
+            remove_source);
     } catch(const std::exception& ex) {
         throw Exception{ex.what()};
     }
@@ -360,12 +292,11 @@ void ProviderManager::restoreProvider(
     auto                       it = self->resolveSpec(provider);
     if (it == self->m_providers.end())
         throw BEDROCK_DETAILED_EXCEPTION("Provider with spec \"{}\" not found", provider);
-    // call the provider's restoreProvider callback
+    ComponentPtr theProvider = (*it)->getHandle<ComponentPtr>();
     try {
-        (*it)->factory->restoreProvider(
-                (*it)->getHandle<void*>(),
-                src_path.c_str(),
-                restore_config.c_str());
+        theProvider->restore(
+            src_path.c_str(),
+            restore_config.c_str());
     } catch(const std::exception& ex) {
         throw Exception{ex.what()};
     }
diff --git a/src/ProviderManagerImpl.hpp b/src/ProviderManagerImpl.hpp
index 3e43ca6..7319a0a 100644
--- a/src/ProviderManagerImpl.hpp
+++ b/src/ProviderManagerImpl.hpp
@@ -10,7 +10,7 @@
 #include "bedrock/DependencyFinder.hpp"
 #include "bedrock/DependencyMap.hpp"
 #include "bedrock/RequestResult.hpp"
-#include "bedrock/AbstractServiceFactory.hpp"
+#include "bedrock/AbstractComponent.hpp"
 #include "bedrock/ProviderDescriptor.hpp"
 #include "bedrock/Jx9Manager.hpp"
 #include "bedrock/Exception.hpp"
@@ -34,49 +34,42 @@ class LocalProvider : public ProviderDependency {
 
     public:
 
-    std::shared_ptr<NamedDependency> pool;
-    ResolvedDependencyMap            dependencies;
-    AbstractServiceFactory*          factory = nullptr;
-    std::vector<std::string>         tags;
+    std::vector<Dependency>  requested_dependencies;
+    ResolvedDependencyMap    resolved_dependencies;
+    std::vector<std::string> tags;
 
     LocalProvider(
-        std::string name, std::string type, uint16_t provider_id,
-        void* handle, AbstractServiceFactory* factory,
-        std::shared_ptr<NamedDependency> pool,
-        ResolvedDependencyMap deps,
-        std::vector<std::string> _tags)
-    : ProviderDependency(std::move(name), std::move(type), handle,
-        [factory](void* handle) {
-            if(factory) factory->deregisterProvider(handle);
-        }, provider_id)
-    , pool(std::move(pool))
-    , dependencies(std::move(deps))
-    , factory(factory)
-    , tags(std::move(_tags))
-    {}
+            std::string name, std::string type, uint16_t provider_id, ComponentPtr ptr,
+            std::vector<Dependency> req_deps, ResolvedDependencyMap res_deps,
+            std::vector<std::string> _tags)
+        : ProviderDependency(std::move(name), std::move(type), ptr, provider_id)
+        , requested_dependencies(std::move(req_deps))
+        , resolved_dependencies(std::move(res_deps))
+        , tags(std::move(_tags))
+    {
+    }
 
     json makeConfig() const {
+        ComponentPtr ptr  = getHandle<ComponentPtr>();
         auto c            = json::object();
         c["name"]         = getName();
         c["type"]         = getType();
         c["provider_id"]  = getProviderID();
-        c["pool"]         = pool->getName();
-        c["config"]       = json::parse(factory->getProviderConfig(getHandle<void*>()));
+        c["config"]       = json::parse(ptr->getConfig());
         c["tags"]         = json::array();
         for(auto& t : tags) c["tags"].push_back(t);
         c["dependencies"] = json::object();
         auto& d           = c["dependencies"];
-        for (auto& p : dependencies) {
-            auto& dep_name  = p.first;
-            auto& dep_group = p.second;
-            if (dep_group.dependencies.size() == 0) continue;
-            if (!dep_group.is_array) {
-                d[dep_name] = dep_group.dependencies[0]->getName();
-            } else {
-                d[dep_name] = json::array();
-                for (auto& x : dep_group.dependencies) {
-                    d[dep_name].push_back(x->getName());
+        for (auto& dep : requested_dependencies) {
+            auto res_dep = resolved_dependencies.find(dep.name);
+            if(res_dep == resolved_dependencies.end()) continue;
+            if(dep.is_array) {
+                d[dep.name] = json::array();
+                for(auto& x : res_dep->second) {
+                    d[dep.name].push_back(x->getName());
                 }
+            } else {
+                d[dep.name] = res_dep->second[0]->getName();
             }
         }
         return c;
@@ -99,7 +92,6 @@ class ProviderManagerImpl
     tl::auto_remote_procedure m_lookup_provider;
     tl::auto_remote_procedure m_load_module;
     tl::auto_remote_procedure m_start_provider;
-    tl::auto_remote_procedure m_change_provider_pool;
     tl::auto_remote_procedure m_migrate_provider;
     tl::auto_remote_procedure m_snapshot_provider;
     tl::auto_remote_procedure m_restore_provider;
@@ -113,8 +105,6 @@ class ProviderManagerImpl
                            &ProviderManagerImpl::loadModuleRPC, pool)),
       m_start_provider(define("bedrock_start_provider",
                               &ProviderManagerImpl::startProviderRPC, pool)),
-      m_change_provider_pool(define("bedrock_change_provider_pool",
-                              &ProviderManagerImpl::changeProviderPoolRPC, pool)),
       m_migrate_provider(define("bedrock_migrate_provider",
                                  &ProviderManagerImpl::migrateProviderRPC, pool)),
       m_snapshot_provider(define("bedrock_snapshot_provider",
@@ -199,12 +189,12 @@ class ProviderManagerImpl
         }
     }
 
-    void loadModuleRPC(const tl::request& req, const std::string& name,
+    void loadModuleRPC(const tl::request& req,
                        const std::string& path) {
         RequestResult<bool> result;
         tl::auto_respond<decltype(result)> auto_respond_with{req, result};
         try {
-            ModuleContext::loadModule(name, path);
+            ModuleManager::loadModule(path);
             result.success() = true;
             result.value()   = true;
         } catch (const Exception& e) {
@@ -226,19 +216,6 @@ class ProviderManagerImpl
         }
     }
 
-    void changeProviderPoolRPC(const tl::request& req, const std::string& name,
-                               const std::string& pool) {
-        RequestResult<bool> result;
-        tl::auto_respond<decltype(result)> auto_respond_with{req, result};
-        auto manager = ProviderManager(shared_from_this());
-        try {
-            manager.changeProviderPool(name, pool);
-        } catch (Exception& ex) {
-            result.success() = false;
-            result.error() = ex.what();
-        }
-    }
-
     void migrateProviderRPC(const tl::request& req,
                             const std::string& name,
                             const std::string& dest_addr,
diff --git a/src/SSGManager.cpp b/src/SSGManager.cpp
deleted file mode 100644
index b3b4ba3..0000000
--- a/src/SSGManager.cpp
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#include <bedrock/SSGManager.hpp>
-#include <bedrock/MargoManager.hpp>
-#include <bedrock/DetailedException.hpp>
-#include "JsonUtil.hpp"
-#include "SSGManagerImpl.hpp"
-#include <margo.h>
-#include <nlohmann/json.hpp>
-#include <spdlog/spdlog.h>
-#ifdef ENABLE_SSG
-  #include <ssg.h>
-  #ifdef ENABLE_MPI
-    #include <ssg-mpi.h>
-  #endif
-  #ifdef ENABLE_PMIX
-    #include <ssg-pmix.h>
-  #endif
-#endif
-#include <regex>
-#include <fstream>
-
-namespace bedrock {
-
-using nlohmann::json;
-using namespace std::string_literals;
-
-int SSGManagerImpl::s_num_ssg_init = 0;
-#ifdef ENABLE_PMIX
-bool SSGManagerImpl::s_initialized_pmix = false;
-#endif
-
-class SSGUpdateHandler {
-#ifdef ENABLE_SSG
-    public:
-    static void membershipUpdate(void* group_data, ssg_member_id_t member_id,
-                                 ssg_member_update_type_t update_type);
-#endif
-};
-
-SSGManager::SSGManager(const MargoManager& margo,
-                       const Jx9Manager& jx9,
-                       const json& config)
-: self(std::make_shared<SSGManagerImpl>()) {
-    self->m_margo_manager = margo;
-    self->m_jx9_manager   = jx9;
-
-    if(config.is_null()) return;
-    if(!(config.is_array() || config.is_object()))
-        throw BEDROCK_DETAILED_EXCEPTION("\"ssg\" field must be an object or an array");
-
-#ifndef ENABLE_SSG
-    if (!config.empty()) {
-        throw BEDROCK_DETAILED_EXCEPTION(
-            "Configuration has \"ssg\" entry but Bedrock wasn't compiled with SSG support");
-    }
-#else
-    if (config.is_object()) {
-        addGroupFromJSON(config);
-    } else if (config.is_array()) {
-        for (const auto& c : config) {
-            addGroupFromJSON(c);
-        }
-    }
-
-    self->updateJx9Ranks();
-#endif
-}
-
-// LCOV_EXCL_START
-
-SSGManager::SSGManager(const SSGManager&) = default;
-
-SSGManager::SSGManager(SSGManager&&) = default;
-
-SSGManager& SSGManager::operator=(const SSGManager&) = default;
-
-SSGManager& SSGManager::operator=(SSGManager&&) = default;
-
-SSGManager::~SSGManager() = default;
-
-SSGManager::operator bool() const { return static_cast<bool>(self); }
-
-// LCOV_EXCL_STOP
-
-std::shared_ptr<NamedDependency> SSGManager::getGroup(const std::string& group_name) const {
-#ifdef ENABLE_SSG
-    auto it = std::find_if(self->m_ssg_groups.begin(), self->m_ssg_groups.end(),
-                           [&](auto& g) { return g->getName() == group_name; });
-    if (it == self->m_ssg_groups.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find SSG group with name \"{}\"", group_name);
-        return nullptr;
-    } else {
-        return *it;
-    }
-#else
-    (void)group_name;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiler with SSG support");
-    return nullptr;
-#endif
-}
-
-std::shared_ptr<NamedDependency> SSGManager::getGroup(size_t group_index) const {
-#ifdef ENABLE_SSG
-    if(group_index >= self->m_ssg_groups.size()) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not find SSG group at index {}", group_index);
-        return nullptr;
-    }
-    return self->m_ssg_groups[group_index];
-#else
-    (void)group_index;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiler with SSG support");
-    return nullptr;
-#endif
-}
-
-size_t SSGManager::getNumGroups() const {
-#ifdef ENABLE_SSG
-    return self->m_ssg_groups.size();
-#else
-    return 0;
-#endif
-}
-
-std::shared_ptr<NamedDependency>
-SSGManager::addGroup(const std::string&                      name,
-                     const ssg_group_config_t&               config,
-                     const std::shared_ptr<NamedDependency>& pool,
-                     const std::string&                      bootstrap,
-                     const std::string&                      group_file) {
-#ifndef ENABLE_SSG
-    (void)name;
-    (void)config;
-    (void)pool;
-    (void)bootstrap;
-    (void)group_file;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock wasn't compiled with SSG support");
-    return nullptr;
-#else // ENABLE_SSG
-    int            ret;
-    ssg_group_id_t gid = SSG_GROUP_ID_INVALID;
-    auto           margo = MargoManager(self->m_margo_manager);
-    auto           mid = margo.getMargoInstance();
-
-    auto it = std::find_if(self->m_ssg_groups.begin(), self->m_ssg_groups.end(),
-                           [&](auto& g) { return g->getName() == name; });
-    if (it != self->m_ssg_groups.end()) {
-        throw BEDROCK_DETAILED_EXCEPTION("SSG group name \"{}\" already used", name);
-    }
-
-    if (SSGManagerImpl::s_num_ssg_init == 0) {
-        int ret = ssg_init();
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Could not initialize SSG (ssg_init returned {})", ret);
-        }
-    }
-    SSGManagerImpl::s_num_ssg_init += 1;
-
-    // The inner data of the ssg_entry will be set later.
-    // The ssg_entry needs to be created here because the
-    // membership callback needs it.
-    auto ssg_entry = std::make_shared<SSGEntry>(self, name, pool);
-
-    spdlog::trace("Creating SSG group {} with bootstrap method {}", name,
-                  bootstrap);
-
-    (void)pool; // TODO add support for specifying the pool
-
-    auto method = bootstrap;
-
-    if (method == "init|join"
-    ||  method == "mpi|join"
-    ||  method == "pmix|join") {
-        auto sep = method.find('|');
-        if(group_file.empty()) {
-            auto new_method = method.substr(0, sep);
-            spdlog::trace("Group file not specified, changing bootstrap method from \"{}\" to \"{}\"",
-                method, new_method);
-            method = std::move(new_method);
-        } else {
-            std::ifstream f(group_file.c_str());
-            if(f.good()) {
-                auto new_method = method.substr(sep+1);
-                spdlog::trace("File {} found, changing bootstrap method from \"{}\" to \"{}\"",
-                    group_file, method, new_method);
-                method = std::move(new_method);
-            } else {
-                auto new_method = method.substr(0, sep);
-                spdlog::trace("File {} not found, changing bootstrap method from \"{}\" to \"{}\"",
-                    group_file, method, new_method);
-                method = std::move(new_method);
-            }
-        }
-    }
-
-    if (method == "init") {
-
-        std::string addr_str = margo.getThalliumEngine().self();
-        std::vector<const char*> addresses = {addr_str.c_str()};
-
-        ret = ssg_group_create(mid, name.c_str(), addresses.data(), 1,
-                               const_cast<ssg_group_config_t*>(&config),
-                               SSGUpdateHandler::membershipUpdate, ssg_entry.get(),
-                               &gid);
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION("ssg_group_create failed with error code {}", ret);
-        }
-
-    } else if (method == "join") {
-
-        if (group_file.empty()) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "SSG \"join\" bootstrapping mode requires a group file to be "
-                "specified");
-        }
-        int num_addrs = 32; // XXX make that configurable?
-        ret           = ssg_group_id_load(group_file.c_str(), &num_addrs, &gid);
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Failed to load SSG group from file {}"
-                " (ssg_group_id_load returned {}",
-                group_file, ret);
-        }
-        ret = ssg_group_join(mid, gid, SSGUpdateHandler::membershipUpdate, ssg_entry.get());
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Failed to join SSG group {} "
-                "(ssg_group_join returned {})",
-                name, ret);
-        }
-
-    } else if (method == "mpi") {
-#ifdef ENABLE_MPI
-        ret = ssg_group_create_mpi(
-            mid, name.c_str(), MPI_COMM_WORLD,
-            const_cast<ssg_group_config_t*>(&config),
-            SSGUpdateHandler::membershipUpdate, ssg_entry.get(), &gid);
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                "Failed to create SSG group {} "
-                "(ssg_group_create_mpi returned {})",
-                name, ret);
-        }
-#else // ENABLE_MPI
-        throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiled with MPI support");
-#endif // ENABLE_MPI
-
-    } else if (method == "pmix") {
-#ifdef ENABLE_PMIX
-        pmix_proc_t proc;
-        auto        ret = PMIx_Init(&proc, NULL, 0);
-        if (ret != PMIX_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION("Could not initialize PMIx (PMIx_Init returned {})",
-                            ret);
-        }
-        gid = ssg_group_create_pmix(
-            mid, name.c_str(), proc,
-            const_cast<ssg_group_config_t*>(config),
-            SSGUpdateHandler::membershipUpdate, group_data.get());
-#else // ENABLE_PMIX
-        throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not compiled with PMIx support");
-#endif // ENABLE_PMIX
-    } else {
-        throw BEDROCK_DETAILED_EXCEPTION("Invalid SSG bootstrapping method {}", bootstrap);
-    }
-    int rank = -1;
-    ret      = ssg_get_group_self_rank(gid, &rank);
-    if (rank == -1 || ret != SSG_SUCCESS) {
-        throw BEDROCK_DETAILED_EXCEPTION("Could not get SSG group rank from group {}", name);
-    }
-    if (!group_file.empty() && (rank == 0)
-        && (method == "init" || method == "mpi"
-            || method == "pmix")) {
-        ret = ssg_group_id_store(group_file.c_str(), gid, SSG_ALL_MEMBERS);
-        if (ret != SSG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION(
-                    "Could not write SSG group file {}"
-                    " (ssg_group_id_store return {}",
-                    group_file, ret);
-        }
-    }
-    ssg_entry->setSSGid(gid);
-    ssg_entry->config        = config;
-    ssg_entry->bootstrap     = bootstrap;
-    ssg_entry->group_file    = group_file;
-    ssg_entry->pool          = pool;
-    ssg_entry->rank          = rank;
-    self->m_ssg_groups.push_back(std::move(ssg_entry));
-
-    self->updateJx9Ranks();
-
-    return ssg_entry;
-#endif // ENABLE_SSSG
-}
-
-std::shared_ptr<NamedDependency>
-SSGManager::addGroupFromJSON(const json& description) {
-#ifndef ENABLE_SSG
-    (void)description;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock wasn't compiled with SSG support");
-    return 0;
-#else // ENABLE_SSG
-
-    static json const configSchema = R"(
-    {
-        "$schema": "https://json-schema.org/draft/2019-09/schema",
-        "type": "object",
-        "properties": {
-            "name": {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-            "pool": {"oneOf": [
-                {"type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" },
-                {"type": "integer", "minimum": 0 }
-            ]},
-            "credential": {"type": "integer"},
-            "group_file": {"type": "string"},
-            "bootstrap": {"enum": ["init", "mpi", "pmix", "join", "init|join", "mpi|join", "pmix|join"]},
-            "swim": {
-                "type": "object",
-                "properties": {
-                    "period_length_ms": {"type": "integer"},
-                    "suspect_timeout_periods": {"type": "integer"},
-                    "subgroup_member_count": {"type": "integer"},
-                    "disabled": {"type": "boolean"}
-                }
-            }
-        },
-        "required": ["name"]
-    }
-    )"_json;
-    static const JsonValidator validator{configSchema};
-    validator.validate(description, "SSGManager");
-    auto name = description["name"].get<std::string>();
-    std::shared_ptr<NamedDependency> pool;
-    if(description.contains("pool") && description["pool"].is_number()) {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description["pool"].get<uint32_t>());
-    } else {
-        pool = MargoManager(self->m_margo_manager)
-            .getPool(description.value("pool", "__primary__"));
-    }
-    auto bootstrap = description.value("bootstrap", "init");
-    auto group_file = description.value("group_file", "");
-    ssg_group_config_t config = SSG_GROUP_CONFIG_INITIALIZER;
-    if(description.contains("credential"))
-        config.ssg_credential = description["credential"].get<int64_t>();
-    if(description.contains("swim") && !description["swim"].value("disabled", false)) {
-        auto& swim = description["swim"];
-        config.swim_disabled                = 0;
-        config.swim_period_length_ms        = swim.value("period_length_ms", 0);
-        config.swim_suspect_timeout_periods = swim.value("suspect_timeout_periods", -1);
-        config.swim_subgroup_member_count   = swim.value("subgroup_member_count", -1);
-    } else {
-        config.swim_disabled = 1;
-    }
-    return addGroup(name, config, pool, bootstrap, group_file);
-#endif // ENABLE_SSG
-}
-
-#ifdef ENABLE_SSG
-void SSGUpdateHandler::membershipUpdate(void* group_data, ssg_member_id_t member_id,
-                                        ssg_member_update_type_t update_type) {
-    SSGEntry* gdata = reinterpret_cast<SSGEntry*>(group_data);
-    (void)gdata;
-    spdlog::trace("SSG membership updated: member_id={}, update_type={}",
-                  member_id, std::to_string(update_type));
-    int self_rank;
-    ssg_get_group_self_rank(gdata->getHandle<ssg_group_id_t>(), &self_rank);
-    gdata->rank = (uint64_t)self_rank;
-    auto manager = gdata->owner.lock();
-    if(manager) manager->updateJx9Ranks();
-}
-#endif
-
-hg_addr_t SSGManager::resolveAddress(const std::string& address) const {
-#ifndef ENABLE_SSG
-    (void)address;
-    throw BEDROCK_DETAILED_EXCEPTION("Bedrock wasn't compiled with SSG support");
-#else // ENABLE_SSG
-    std::regex  re("ssg:\\/\\/([a-zA-Z_][a-zA-Z0-9_]*)\\/(#?)([0-9][0-9]*)$");
-    std::smatch match;
-    if (std::regex_search(address, match, re)) {
-        auto group_name        = match.str(1);
-        auto is_member_id      = match.str(2) == "#";
-        auto member_id_or_rank = atol(match.str(3).c_str());
-        auto ssg_entry         = getGroup(group_name);
-        if (!ssg_entry) {
-            throw BEDROCK_DETAILED_EXCEPTION("Could not resolve \"{}\" to a valid SSG group",
-                            group_name);
-        }
-        ssg_member_id_t member_id;
-        auto gid = ssg_entry->getHandle<ssg_group_id_t>();
-        if (!is_member_id) {
-            int ret = ssg_get_group_member_id_from_rank(gid, member_id_or_rank,
-                                                        &member_id);
-            if (member_id == SSG_MEMBER_ID_INVALID || ret != SSG_SUCCESS) {
-                throw BEDROCK_DETAILED_EXCEPTION("Invalid rank {} in group {}",
-                                member_id_or_rank, group_name);
-            }
-        } else {
-            member_id = member_id_or_rank;
-        }
-        hg_addr_t addr = HG_ADDR_NULL;
-        int       ret  = ssg_get_group_member_addr(gid, member_id, &addr);
-        if (addr == HG_ADDR_NULL || ret != HG_SUCCESS) {
-            throw BEDROCK_DETAILED_EXCEPTION("Invalid member id {} in group {}", member_id,
-                            group_name);
-        }
-        return addr;
-    } else {
-        throw BEDROCK_DETAILED_EXCEPTION("Invalid SSG address specification \"{}\"", address);
-    }
-    return HG_ADDR_NULL;
-#endif // ENABLE_SSG
-}
-
-json SSGManager::getCurrentConfig() const {
-#ifndef ENABLE_SSG
-    return json::array();
-#else
-    return self->makeConfig();
-#endif
-}
-
-} // namespace bedrock
diff --git a/src/SSGManagerImpl.hpp b/src/SSGManagerImpl.hpp
deleted file mode 100644
index 252cc01..0000000
--- a/src/SSGManagerImpl.hpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * (C) 2020 The University of Chicago
- *
- * See COPYRIGHT in top-level directory.
- */
-#ifndef __BEDROCK_SSG_CONTEXT_IMPL_H
-#define __BEDROCK_SSG_CONTEXT_IMPL_H
-
-#include <bedrock/MargoManager.hpp>
-#include <bedrock/SSGManager.hpp>
-#include <bedrock/Jx9Manager.hpp>
-#include <bedrock/NamedDependency.hpp>
-#include <bedrock/DetailedException.hpp>
-#include "MargoManagerImpl.hpp"
-#include <spdlog/spdlog.h>
-#include <memory>
-#include <vector>
-#include <string>
-
-#ifdef ENABLE_SSG
-#include <ssg.h>
-
-namespace std {
-
-static inline auto to_string(ssg_member_update_type_t t) -> std::string {
-    switch(t) {
-        case SSG_MEMBER_JOINED: return "SSG_MEMBER_JOINED";
-        case SSG_MEMBER_LEFT:   return "SSG_MEMBER_LEFT";
-        case SSG_MEMBER_DIED:   return "SSG_MEMBER_DIED";
-        default: return "UNKNOWN";
-    }
-}
-
-}
-
-#endif
-
-namespace bedrock {
-
-class SSGManagerImpl;
-
-class SSGEntry : public NamedDependency {
-
-    public:
-
-#ifdef ENABLE_SSG
-    ssg_group_config_t               config;
-    std::string                      bootstrap;
-    std::string                      group_file;
-    std::shared_ptr<NamedDependency> pool;
-    std::weak_ptr<SSGManagerImpl>    owner;
-
-    uint64_t                         rank; // current rank of the process in this group
-
-    SSGEntry(std::shared_ptr<SSGManagerImpl> o, std::string name, std::shared_ptr<NamedDependency> p)
-    : NamedDependency(
-        std::move(name),
-        "ssg", SSG_GROUP_ID_INVALID,
-        std::function<void(void*)>())
-    , pool{std::move(p)}
-    , owner(o)
-    {}
-
-    SSGEntry(const SSGEntry&) = delete;
-    SSGEntry(SSGEntry&&) = delete;
-
-    void setSSGid(ssg_group_id_t gid) {
-        m_handle = reinterpret_cast<void*>(gid);
-    }
-
-    ssg_group_id_t getSSGid() const {
-        return reinterpret_cast<ssg_group_id_t>(m_handle);
-    }
-
-    json makeConfig() const {
-        json c          = json::object();
-        c["name"]       = getName();
-        c["bootstrap"]  = bootstrap;
-        c["group_file"] = group_file;
-        c["pool"]       = pool->getName();
-        c["credential"] = config.ssg_credential;
-        if(!config.swim_disabled) {
-            c["swim"]       = json::object();
-            auto& swim      = c["swim"];
-            swim["period_length_ms"]        = config.swim_period_length_ms;
-            swim["suspect_timeout_periods"] = config.swim_suspect_timeout_periods;
-            swim["subgroup_member_count"]   = config.swim_subgroup_member_count;
-        }
-        return c;
-    }
-
-#else
-
-    json makeConfig() const {
-        return json::object();
-    }
-
-#endif
-};
-
-class SSGManagerImpl {
-
-  public:
-    std::shared_ptr<MargoManagerImpl>         m_margo_manager;
-    std::shared_ptr<Jx9ManagerImpl>           m_jx9_manager;
-    std::vector<std::shared_ptr<SSGEntry>>    m_ssg_groups;
-
-    json makeConfig() const {
-        auto config = json::array();
-        for (const auto& g : m_ssg_groups) {
-            config.push_back(g->makeConfig());
-        }
-        return config;
-    }
-
-#ifdef ENABLE_SSG
-    SSGManagerImpl() {
-        if (SSGManagerImpl::s_num_ssg_init == 0) {
-            int ret = ssg_init();
-            if (ret != SSG_SUCCESS) {
-                throw BEDROCK_DETAILED_EXCEPTION("Could not initialize SSG (ssg_init returned {})",
-                        ret);
-            }
-        }
-        SSGManagerImpl::s_num_ssg_init += 1;
-    }
-
-    static void releaseSSGid(ssg_group_id_t gid) {
-        if (!gid) return;
-        int ret = ssg_group_leave(gid);
-        // if SWIM is disabled, this function will return SSG_ERR_NOT_SUPPORTED
-        if (ret != SSG_SUCCESS && ret != SSG_ERR_NOT_SUPPORTED) {
-            spdlog::error(
-                    "Could not leave SSG group (ssg_group_leave returned {})",
-                    ret);
-        }
-        ret = ssg_group_destroy(gid);
-        if (ret != SSG_SUCCESS) {
-            spdlog::error(
-                    "Could not destroy SSG group (ssg_group_destroy returned {})",
-                    ret);
-        }
-    }
-
-    ~SSGManagerImpl() {
-        spdlog::trace("Destroying SSGManager (count = {})", s_num_ssg_init);
-        m_ssg_groups.clear();
-        s_num_ssg_init -= 1;
-        if (s_num_ssg_init == 0) {
-            spdlog::trace("Finalizing SSG");
-            ssg_finalize();
-        }
-    }
-
-    void updateJx9Ranks() {
-        json rankMap = json::object();
-        for(auto& g : m_ssg_groups) {
-            auto& x = rankMap[g->getName()] = json::object();
-            x["rank"] = g->rank;
-        }
-        Jx9Manager(m_jx9_manager).setVariable(
-            "__ssg__", rankMap.dump());
-    }
-#endif
-
-    void clear() {
-#ifdef ENABLE_SSG
-        for(auto& g : m_ssg_groups) {
-            releaseSSGid(g->getHandle<ssg_group_id_t>());
-        }
-        m_ssg_groups.resize(0);
-#endif
-    }
-
-
-    static int s_num_ssg_init;
-#ifdef ENABLE_PMIX
-    static bool s_initialized_pmix;
-#endif
-
-};
-
-} // namespace bedrock
-
-#endif
diff --git a/src/Server.cpp b/src/Server.cpp
index de4b57b..b6c3846 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -5,13 +5,9 @@
  */
 #include <bedrock/Server.hpp>
 #include <bedrock/MargoManager.hpp>
-#include <bedrock/ABTioManager.hpp>
-#include <bedrock/MonaManager.hpp>
-#include <bedrock/ModuleContext.hpp>
+#include <bedrock/ModuleManager.hpp>
 #include <bedrock/ProviderManager.hpp>
-#include <bedrock/ClientManager.hpp>
 #include <bedrock/DependencyFinder.hpp>
-#include <bedrock/SSGManager.hpp>
 #include <bedrock/Jx9Manager.hpp>
 #include <bedrock/Exception.hpp>
 #include "MargoLogging.hpp"
@@ -139,27 +135,6 @@ Server::Server(const std::string& address, const std::string& configString,
 
     try {
 
-        // Initializing SSG context
-        spdlog::trace("Initializing SSGManager");
-        auto& ssgConfig     = config["ssg"];
-        auto ssgMgr         = SSGManager(margoMgr, jx9Manager, ssgConfig);
-        self->m_ssg_manager = ssgMgr;
-        spdlog::trace("SSGManager initialized");
-
-        // Initialize abt-io context
-        spdlog::trace("Initializing ABTioManager");
-        auto& abtioConfig     = config["abt_io"];
-        auto abtioMgr         = ABTioManager(margoMgr, jx9Manager, abtioConfig);
-        self->m_abtio_manager = abtioMgr;
-        spdlog::trace("ABTioManager initialized");
-
-        // Initialize mona manager
-        spdlog::trace("Initializing MonaManager");
-        auto& monaConfig     = config["mona"];
-        auto monaMgr         = MonaManager(margoMgr, jx9Manager, monaConfig, address);
-        self->m_mona_manager = monaMgr;
-        spdlog::trace("MonaManager initialized");
-
         // Initializing the provider manager
         spdlog::trace("Initializing ProviderManager");
         auto providerManager
@@ -167,33 +142,19 @@ Server::Server(const std::string& address, const std::string& configString,
         self->m_provider_manager = providerManager;
         spdlog::trace("ProviderManager initialized");
 
-        // Initializing the client manager
-        spdlog::trace("Initializing ClientManager");
-        auto clientManager
-            = ClientManager(margoMgr, jx9Manager, bedrock_provider_id, bedrock_pool);
-        self->m_client_manager = clientManager;
-        spdlog::trace("ClientManager initialized");
-
         // Initialize the module context
         spdlog::trace("Initialize ModuleContext");
         auto librariesConfig = config["libraries"].dump();
-        ModuleContext::loadModulesFromJSON(librariesConfig);
+        ModuleManager::loadModulesFromJSON(librariesConfig);
         spdlog::trace("ModuleContext initialized");
 
         // Initializing dependency finder
         spdlog::trace("Initializing DependencyFinder");
-        auto dependencyFinder     = DependencyFinder(
-            mpi, margoMgr, abtioMgr, ssgMgr, monaMgr, providerManager, clientManager);
+        auto dependencyFinder     = DependencyFinder(mpi, margoMgr, providerManager);
         self->m_dependency_finder = dependencyFinder;
         self->m_dependency_finder->m_timeout = dependency_timeout;
         spdlog::trace("DependencyFinder initialized");
 
-        // Creating clients
-        spdlog::trace("Initializing clients");
-        clientManager.setDependencyFinder(dependencyFinder);
-        auto& clientManagerConfig = config["clients"];
-        clientManager.addClientListFromJSON(clientManagerConfig);
-
         // Starting up providers
         spdlog::trace("Initializing providers");
         auto& providerManagerConfig = config["providers"];
@@ -214,30 +175,18 @@ Server::~Server() {}
 
 MargoManager Server::getMargoManager() const { return self->m_margo_manager; }
 
-ABTioManager Server::getABTioManager() const { return self->m_abtio_manager; }
-
 ProviderManager Server::getProviderManager() const {
     return self->m_provider_manager;
 }
 
-ClientManager Server::getClientManager() const {
-    return self->m_client_manager;
-}
-
-SSGManager Server::getSSGManager() const { return self->m_ssg_manager; }
-
-void Server::onPreFinalize(void* uargs) {
+void Server::onPreFinalize() {
     spdlog::trace("Calling Server's pre-finalize callback");
-    auto server = reinterpret_cast<Server*>(uargs);
-    if(server->self) {
-        if(server->self->m_ssg_manager)
-            server->self->m_ssg_manager->clear();
-        if(server->self->m_provider_manager)
-            server->self->m_provider_manager.reset();
+    if(self && self->m_provider_manager) {
+        self->m_provider_manager.reset();
     }
 }
 
-void Server::onFinalize(void* uargs) {
+void Server::onFinalize() {
     spdlog::trace("Calling Server's finalize callback");
 }
 
@@ -246,19 +195,17 @@ std::string Server::getCurrentConfig() const {
 }
 
 void Server::waitForFinalize() {
-    margo_push_finalize_callback(self->m_margo_manager->m_mid,
-                                 &Server::onFinalize, this);
-    margo_push_prefinalize_callback(self->m_margo_manager->m_mid,
-                                 &Server::onPreFinalize, this);
-    margo_wait_for_finalize(self->m_margo_manager->m_mid);
+    auto engine = getMargoManager().getThalliumEngine();
+    engine.push_finalize_callback([this]() { onFinalize(); });
+    engine.push_prefinalize_callback([this]() { onPreFinalize(); });
+    engine.wait_for_finalize();
 }
 
 void Server::finalize() {
-    margo_push_finalize_callback(self->m_margo_manager->m_mid,
-                                 &Server::onFinalize, this);
-    margo_push_prefinalize_callback(self->m_margo_manager->m_mid,
-                                 &Server::onPreFinalize, this);
-    margo_finalize_and_wait(self->m_margo_manager->m_mid);
+    auto engine = getMargoManager().getThalliumEngine();
+    engine.push_finalize_callback([this]() { onFinalize(); });
+    engine.push_prefinalize_callback([this]() { onPreFinalize(); });
+    engine.finalize_and_wait();
 }
 
 } // namespace bedrock
diff --git a/src/ServerImpl.hpp b/src/ServerImpl.hpp
index 44808d0..62e4e5c 100644
--- a/src/ServerImpl.hpp
+++ b/src/ServerImpl.hpp
@@ -7,17 +7,13 @@
 #define __BEDROCK_SERVER_IMPL_H
 
 #include "MargoManagerImpl.hpp"
-#include "ABTioManagerImpl.hpp"
-#include "MonaManagerImpl.hpp"
 #include "ProviderManagerImpl.hpp"
-#include "ClientManagerImpl.hpp"
 #include "DependencyFinderImpl.hpp"
-#include "SSGManagerImpl.hpp"
 #include "Jx9ManagerImpl.hpp"
 #include "MPIEnvImpl.hpp"
 #include "bedrock/Jx9Manager.hpp"
 #include "bedrock/RequestResult.hpp"
-#include "bedrock/ModuleContext.hpp"
+#include "bedrock/ModuleManager.hpp"
 #include <thallium/serialization/stl/string.hpp>
 #include <nlohmann/json.hpp>
 #include <spdlog/spdlog.h>
@@ -34,10 +30,6 @@ class ServerImpl : public tl::provider<ServerImpl> {
     std::shared_ptr<MPIEnvImpl>           m_mpi;
     std::shared_ptr<Jx9ManagerImpl>       m_jx9_manager;
     std::shared_ptr<MargoManagerImpl>     m_margo_manager;
-    std::shared_ptr<ABTioManagerImpl>     m_abtio_manager;
-    std::shared_ptr<MonaManagerImpl>      m_mona_manager;
-    std::shared_ptr<SSGManagerImpl>       m_ssg_manager;
-    std::shared_ptr<ClientManagerImpl>    m_client_manager;
     std::shared_ptr<ProviderManagerImpl>  m_provider_manager;
     std::shared_ptr<DependencyFinderImpl> m_dependency_finder;
     std::shared_ptr<NamedDependency>      m_pool;
@@ -45,8 +37,6 @@ class ServerImpl : public tl::provider<ServerImpl> {
 
     tl::remote_procedure m_get_config_rpc;
     tl::remote_procedure m_query_config_rpc;
-    tl::remote_procedure m_add_abtio_rpc;
-    tl::remote_procedure m_add_ssg_group_rpc;
 
     tl::remote_procedure m_add_pool_rpc;
     tl::remote_procedure m_add_xstream_rpc;
@@ -58,15 +48,11 @@ class ServerImpl : public tl::provider<ServerImpl> {
     : tl::provider<ServerImpl>(margo->m_engine, provider_id, "bedrock"),
       m_margo_manager(std::move(margo)),
       m_pool(pool),
-      m_tl_pool(pool->getHandle<ABT_pool>()),
+      m_tl_pool(pool->getHandle<tl::pool>()),
       m_get_config_rpc(
           define("bedrock_get_config", &ServerImpl::getConfigRPC, m_tl_pool)),
       m_query_config_rpc(
           define("bedrock_query_config", &ServerImpl::queryConfigRPC, m_tl_pool)),
-      m_add_abtio_rpc(
-          define("bedrock_add_abtio", &ServerImpl::addABTioRPC, m_tl_pool)),
-      m_add_ssg_group_rpc(
-          define("bedrock_add_ssg_group", &ServerImpl::addSSGgroupRPC, m_tl_pool)),
       m_add_pool_rpc(
           define("bedrock_add_pool", &ServerImpl::addPoolRPC, m_tl_pool)),
       m_add_xstream_rpc(
@@ -80,8 +66,6 @@ class ServerImpl : public tl::provider<ServerImpl> {
     ~ServerImpl() {
         m_get_config_rpc.deregister();
         m_query_config_rpc.deregister();
-        m_add_abtio_rpc.deregister();
-        m_add_ssg_group_rpc.deregister();
         m_add_pool_rpc.deregister();
         m_add_xstream_rpc.deregister();
         m_remove_pool_rpc.deregister();
@@ -91,12 +75,8 @@ class ServerImpl : public tl::provider<ServerImpl> {
     json makeConfig() const {
         auto config         = json::object();
         config["margo"]     = m_margo_manager->makeConfig();
-        config["abt_io"]    = m_abtio_manager->makeConfig();
-        config["clients"]   = m_client_manager->makeConfig();
         config["providers"] = m_provider_manager->makeConfig();
-        config["ssg"]       = m_ssg_manager->makeConfig();
-        config["mona"]      = m_mona_manager->makeConfig();
-        config["libraries"] = json::parse(ModuleContext::getCurrentConfig());
+        config["libraries"] = json::parse(ModuleManager::getCurrentConfig());
         config["bedrock"]   = json::object();
         config["bedrock"]["pool"] = m_pool->getName();
         config["bedrock"]["provider_id"] = get_provider_id();
@@ -124,30 +104,6 @@ class ServerImpl : public tl::provider<ServerImpl> {
         req.respond(result);
     }
 
-    void addABTioRPC(const tl::request& req, const std::string& description) {
-        RequestResult<bool> result;
-        result.success() = true;
-        try {
-            ABTioManager(m_abtio_manager).addABTioInstanceFromJSON(json::parse(description));
-        } catch (const std::exception& ex) {
-            result.error()   = ex.what();
-            result.success() = false;
-        }
-        req.respond(result);
-    }
-
-    void addSSGgroupRPC(const tl::request& req, const std::string& description) {
-        RequestResult<bool> result;
-        result.success() = true;
-        try {
-            SSGManager(m_ssg_manager).addGroupFromJSON(json::parse(description));
-        } catch (const std::exception& ex) {
-            result.error()   = ex.what();
-            result.success() = false;
-        }
-        req.respond(result);
-    }
-
     void addPoolRPC(const tl::request& req, const std::string& config) {
         RequestResult<bool> result;
         result.success() = true;
diff --git a/src/ServiceGroupHandleImpl.hpp b/src/ServiceGroupHandleImpl.hpp
index 69b0812..a9db3fb 100644
--- a/src/ServiceGroupHandleImpl.hpp
+++ b/src/ServiceGroupHandleImpl.hpp
@@ -11,9 +11,6 @@
 #include "ClientImpl.hpp"
 #include "ServiceHandleImpl.hpp"
 #include "Formatting.hpp"
-#ifdef ENABLE_SSG
-#include <ssg.h>
-#endif
 #ifdef ENABLE_FLOCK
 #include <flock/flock-client.h>
 #include <flock/flock-group.h>
@@ -31,10 +28,6 @@ class ServiceGroupHandleImpl {
     uint16_t                                        m_provider_id;
     std::vector<std::shared_ptr<ServiceHandleImpl>> m_shs;
 
-#ifdef ENABLE_SSG
-    ssg_group_id_t                                  m_gid = SSG_GROUP_ID_INVALID;
-    bool                                            m_owns_gid = false;
-#endif
 #ifdef ENABLE_FLOCK
     flock_client_t                                  m_flock_client = FLOCK_CLIENT_NULL;
     flock_group_handle_t                            m_flock_gh = FLOCK_GROUP_HANDLE_NULL;
@@ -47,10 +40,6 @@ class ServiceGroupHandleImpl {
     : m_client(std::move(client)), m_provider_id(provider_id) {}
 
     ~ServiceGroupHandleImpl() {
-#ifdef ENABLE_SSG
-        if(m_owns_gid)
-            ssg_group_destroy(m_gid);
-#endif
 #ifdef ENABLE_FLOCK
         if(m_flock_gh) flock_group_handle_release(m_flock_gh);
         if(m_flock_client) flock_client_finalize(m_flock_client);
@@ -59,45 +48,6 @@ class ServiceGroupHandleImpl {
 
     std::vector<std::string> queryAddresses(bool refresh) const {
         std::vector<std::string> addresses;
-#ifdef ENABLE_SSG
-        if(m_gid) {
-            auto mid = m_client->m_engine.get_margo_instance();
-            int ret = SSG_SUCCESS;
-            if(refresh)
-                ret = ssg_group_refresh(mid, m_gid);
-            if (ret != SSG_SUCCESS)
-                throw BEDROCK_DETAILED_EXCEPTION(
-                    "Could not refresh SSG group view "
-                    "(ssg_group_refresh returned {})", ret);
-            int group_size = 0;
-            ret = ssg_get_group_size(m_gid, &group_size);
-            if (ret != SSG_SUCCESS)
-                throw BEDROCK_DETAILED_EXCEPTION(
-                    "Could not get SSG group size "
-                    "(ssg_get_group_size returned {})", ret);
-            addresses.reserve(group_size);
-            for (int i = 0; i < group_size; i++) {
-                ssg_member_id_t member_id = SSG_MEMBER_ID_INVALID;
-                ret = ssg_get_group_member_id_from_rank(m_gid, i, &member_id);
-                if (member_id == SSG_MEMBER_ID_INVALID || ret != SSG_SUCCESS) {
-                    throw BEDROCK_DETAILED_EXCEPTION(
-                            "Could not get member ID from rank {} "
-                            "(ssg_get_group_member_id_from_rank returned {})",
-                            i, ret);
-                }
-                char* addr = NULL;
-                ret        = ssg_get_group_member_addr_str(m_gid, member_id, &addr);
-                if (addr == NULL || ret != SSG_SUCCESS) {
-                    throw BEDROCK_DETAILED_EXCEPTION(
-                            "Could not get address from SSG member {} (rank {}) "
-                            "(ssg_get_group_member_addr_str returned {})", member_id,
-                            i, ret);
-                }
-                addresses.emplace_back(addr);
-            }
-            return addresses;
-        }
-#endif
 #if ENABLE_FLOCK
         if(m_flock_gh) {
             flock_return_t ret = flock_group_access_view(m_flock_gh,
@@ -119,46 +69,6 @@ class ServiceGroupHandleImpl {
         throw Exception{"ServiceGroupHandle not associated with an SSG or Flock group"};
     }
 
-    static std::shared_ptr<ServiceGroupHandleImpl> FromSSGfile(
-            std::shared_ptr<ClientImpl> client,
-            const std::string& groupfile,
-            uint16_t provider_id) {
-#ifdef ENABLE_SSG
-        int num_addrs = SSG_ALL_MEMBERS;
-        ssg_group_id_t gid = SSG_GROUP_ID_INVALID;
-        int ret = ssg_group_id_load(groupfile.c_str(), &num_addrs, &gid);
-        if (ret != SSG_SUCCESS)
-            throw BEDROCK_DETAILED_EXCEPTION("Could not load group file {} "
-                    "(ssg_group_id_load returned {})", groupfile, ret);
-        auto sg_impl = std::make_shared<ServiceGroupHandleImpl>(std::move(client), provider_id);
-        sg_impl->m_gid = gid;
-        sg_impl->m_owns_gid = true;
-        return sg_impl;
-#else
-        (void)client;
-        (void)groupfile;
-        (void)provider_id;
-        throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not built with SSG support");
-#endif
-    }
-
-    static std::shared_ptr<ServiceGroupHandleImpl> FromSSGid(
-            std::shared_ptr<ClientImpl> client,
-            uint64_t gid,
-            uint16_t provider_id) {
-#ifdef ENABLE_SSG
-        std::vector<std::string> addresses;
-        auto sg_impl = std::make_shared<ServiceGroupHandleImpl>(client, provider_id);
-        sg_impl->m_gid = gid;
-        return sg_impl;
-#else
-        (void)client;
-        (void)gid;
-        (void)provider_id;
-        throw BEDROCK_DETAILED_EXCEPTION("Bedrock was not built with SSG support");
-#endif
-    }
-
     static std::shared_ptr<ServiceGroupHandleImpl> FromFlockFile(
             std::shared_ptr<ClientImpl> client,
             const std::string& groupfile,
diff --git a/src/ServiceHandle.cpp b/src/ServiceHandle.cpp
index 20290f5..5931737 100644
--- a/src/ServiceHandle.cpp
+++ b/src/ServiceHandle.cpp
@@ -70,12 +70,12 @@ tl::provider_handle ServiceHandle::providerHandle() const {
     } \
 } while(0)
 
-void ServiceHandle::loadModule(const std::string& name, const std::string& path,
+void ServiceHandle::loadModule(const std::string& path,
                                AsyncRequest* req) const {
     if (not self) throw BEDROCK_DETAILED_EXCEPTION("Invalid bedrock::ServiceHandle object");
     auto& rpc = self->m_client->m_load_module;
     auto& ph  = self->m_ph;
-    SEND_RPC_WITH_BOOL_RESULT(name, path);
+    SEND_RPC_WITH_BOOL_RESULT(path);
 }
 
 void ServiceHandle::addProvider(const std::string& description,
@@ -162,22 +162,6 @@ void ServiceHandle::addClient(const std::string& description,
     SEND_RPC_WITH_BOOL_RESULT(description);
 }
 
-void ServiceHandle::addABTioInstance(const std::string& description,
-                                     AsyncRequest*      req) const {
-    if (not self) throw BEDROCK_DETAILED_EXCEPTION("Invalid bedrock::ServiceHandle object");
-    auto& rpc = self->m_client->m_add_abtio;
-    auto& ph  = self->m_ph;
-    SEND_RPC_WITH_BOOL_RESULT(description);
-}
-
-void ServiceHandle::addSSGgroup(const std::string& config,
-                                AsyncRequest*      req) const {
-    if (not self) throw BEDROCK_DETAILED_EXCEPTION("Invalid bedrock::ServiceHandle object");
-    auto& rpc = self->m_client->m_add_ssg_group;
-    auto& ph  = self->m_ph;
-    SEND_RPC_WITH_BOOL_RESULT(config);
-}
-
 void ServiceHandle::addPool(const std::string& config,
                             AsyncRequest*      req) const {
     if (not self) throw BEDROCK_DETAILED_EXCEPTION("Invalid bedrock::ServiceHandle object");
diff --git a/src/bedrock-config.cmake.in b/src/bedrock-config.cmake.in
index 3a01449..98df1d0 100644
--- a/src/bedrock-config.cmake.in
+++ b/src/bedrock-config.cmake.in
@@ -12,21 +12,14 @@ include (CMakeFindDependencyMacro)
 find_dependency (nlohmann_json)
 find_dependency (thallium)
 find_dependency (spdlog)
-find_dependency (PkgConfig)
 find_dependency (fmt)
-pkg_check_modules (MARGO REQUIRED IMPORTED_TARGET margo)
 
 if(@ENABLE_MPI@)
   find_dependency (MPI)
 endif()
-if(@ENABLE_SSG@)
-  pkg_check_modules (SSG REQUIRED IMPORTED_TARGET ssg)
-endif()
-if(@ENABLE_ABT_IO@)
-  pkg_check_modules (ABTIO REQUIRED IMPORTED_TARGET abt-io)
-endif()
-if(@ENABLE_MONA@)
-  pkg_check_modules (MONA REQUIRED IMPORTED_TARGET mona)
+
+if(@ENABLE_FLOCK@)
+    find_dependency (flock)
 endif()
 
 check_required_components(bedrock)
diff --git a/tests/Client.cpp b/tests/Client.cpp
index 3b1f280..9b3d7aa 100644
--- a/tests/Client.cpp
+++ b/tests/Client.cpp
@@ -14,7 +14,6 @@ TEST_CASE("Tests various object creation and removal via a ServiceHandle", "[ser
         auto engine = server.getMargoManager().getThalliumEngine();
         bedrock::Client client(engine);
         auto serviceHandle = client.makeServiceHandle(engine.self(), 0);
-
         SECTION("Add and remove pool remotely") {
             // add a pool called "my_pool1", synchronously
             serviceHandle.addPool("{\"name\":\"my_pool1\",\"kind\":\"fifo_wait\",\"access\":\"mpmc\"}");
@@ -106,58 +105,29 @@ TEST_CASE("Tests various object creation and removal via a ServiceHandle", "[ser
             REQUIRE_THROWS_AS(req.wait(), bedrock::Exception);
         }
 
-        SECTION("Add and remove ABT-IO instances remotely") {
-            // add ABT-IO instance synchronously
-            constexpr const char* my_abt_io1 = R"(
-            { "name": "my_abt_io1", "pool": "__primary__" }
-            )";
-            serviceHandle.addABTioInstance(my_abt_io1);
-            auto output_config = json::parse(server.getCurrentConfig());
-            auto abt_io = output_config["abt_io"];
-            REQUIRE(std::find_if(abt_io.begin(), abt_io.end(),
-                    [](auto& x) { return x["name"] == "my_abt_io1"; })
-                    != abt_io.end());
-            // add ABT-IO instance asynchronously
-            bedrock::AsyncRequest req;
-            constexpr const char* my_abt_io2 = R"(
-            { "name": "my_abt_io2", "pool": "__primary__", "config": {} }
-            )";
-            serviceHandle.addABTioInstance(my_abt_io2, &req);
-            req.wait();
-            output_config = json::parse(server.getCurrentConfig());
-            abt_io = output_config["abt_io"];
-            REQUIRE(std::find_if(abt_io.begin(), abt_io.end(),
-                    [](auto& x) { return x["name"] == "my_abt_io2"; })
-                    != abt_io.end());
-            // add ABT-IO instance with invalid configuration
-            constexpr const char* my_abt_io3 = R"(
-            { "name": "my_abt_io2", "pool": "1234" }
-            )";
-            REQUIRE_THROWS_AS(
-                serviceHandle.addABTioInstance(my_abt_io3),
-                bedrock::Exception);
-            // TODO: add removal when we have the functionality for it
-        }
-
         SECTION("Load a library") {
+            auto server_config = server.getCurrentConfig();
+            REQUIRE(server_config.find("./libModuleA.so") == std::string::npos);
+            REQUIRE(server_config.find("./libModuleB.so") == std::string::npos);
             // load libModuleA.so synchronously
-            serviceHandle.loadModule("module_a", "./libModuleA.so");
-            REQUIRE(bedrock::ModuleContext::getServiceFactory("module_a") != nullptr);
+            serviceHandle.loadModule("./libModuleA.so");
+            server_config = server.getCurrentConfig();
+            REQUIRE(server_config.find("./libModuleA.so") != std::string::npos);
             // load libModuleA.so asynchronously
             bedrock::AsyncRequest req;
-            serviceHandle.loadModule("module_b", "./libModuleB.so", &req);
+            serviceHandle.loadModule("./libModuleB.so", &req);
             req.wait();
-            REQUIRE(bedrock::ModuleContext::getServiceFactory("module_b") != nullptr);
-            // load libModuleC.so, which does not exist
+            server_config = server.getCurrentConfig();
+            REQUIRE(server_config.find("./libModuleB.so") != std::string::npos);
+            // load libModuleX.so, which does not exist
             REQUIRE_THROWS_AS(
-                serviceHandle.loadModule("module_x", "libModuleX.so"),
+                serviceHandle.loadModule("./libModuleX.so"),
                 bedrock::Exception);
         }
 
         SECTION("Add and remove providers") {
             // load module_a
-            serviceHandle.loadModule("module_a", "./libModuleA.so");
-            REQUIRE(bedrock::ModuleContext::getServiceFactory("module_a") != nullptr);
+            serviceHandle.loadModule("./libModuleA.so");
             // create a provider of type module_a
             REQUIRE_NOTHROW(serviceHandle.addProvider(R"(
                 {"name":"my_provider_a1", "type":"module_a", "provider_id":123})"));
diff --git a/tests/InitInvalid.cpp b/tests/InitInvalid.cpp
index 22d0f8e..c4aa8e6 100644
--- a/tests/InitInvalid.cpp
+++ b/tests/InitInvalid.cpp
@@ -1,6 +1,7 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/catch_all.hpp>
 #include <bedrock/Server.hpp>
+#include <bedrock/Exception.hpp>
 #include <nlohmann/json.hpp>
 #include <fstream>
 
diff --git a/tests/InitJx9.cpp b/tests/InitJx9.cpp
index 5691355..8f11055 100644
--- a/tests/InitJx9.cpp
+++ b/tests/InitJx9.cpp
@@ -1,6 +1,7 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/catch_all.hpp>
 #include <bedrock/Server.hpp>
+#include <bedrock/Exception.hpp>
 #include <nlohmann/json.hpp>
 #include <fstream>
 
@@ -9,13 +10,6 @@ using json = nlohmann::json;
 static void cleanupOutputConfig(json& config) {
     config["margo"].erase("mercury");
     config["margo"].erase("version");
-    for(auto& instance : config["abt_io"]) {
-        instance["config"].erase("version");
-    }
-    for(auto& instance : config["mona"]) {
-        if(instance.contains("address"))
-            instance["address"] = "<replaced>";
-    }
 }
 
 static std::string jsonToJx9(const json& config) {
diff --git a/tests/InitValid.cpp b/tests/InitValid.cpp
index 94b6a4d..8c33849 100644
--- a/tests/InitValid.cpp
+++ b/tests/InitValid.cpp
@@ -11,13 +11,6 @@ using json = nlohmann::json;
 static void cleanupOutputConfig(json& config) {
     config["margo"].erase("mercury");
     config["margo"].erase("version");
-    for(auto& instance : config["abt_io"]) {
-        instance["config"].erase("version");
-    }
-    for(auto& instance : config["mona"]) {
-        if(instance.contains("address"))
-            instance["address"] = "<replaced>";
-    }
 }
 
 TEST_CASE("Tests Server initialization", "[init-json]") {
diff --git a/tests/InvalidConfigs.json b/tests/InvalidConfigs.json
index 2ff429a..29519f2 100644
--- a/tests/InvalidConfigs.json
+++ b/tests/InvalidConfigs.json
@@ -1,91 +1,4 @@
 [
-    {
-        "test": "invalid ABT-IO section",
-        "input": {"abt_io":123}
-    },
-
-    {
-        "test": "invalid type of ABT-IO instance configuration",
-        "input": {"abt_io":[123]}
-    },
-
-    {
-        "test": "invalid ABT-IO instance name",
-        "input": {"abt_io":[{"name":123}]}
-    },
-
-    {
-        "test": "invalid ABT-IO instance pool name",
-        "input": {"abt_io":[{"pool":"abc"}]}
-    },
-
-    {
-        "test": "invalid ABT-IO instance pool index",
-        "input": {"abt_io":[{"pool":42}]}
-    },
-
-    {
-        "test": "invalid ABT-IO instance pool field type",
-        "input": {"abt_io":[{"pool":true}]}
-    },
-
-    {
-        "test": "invalid ABT-IO instance config field type",
-        "input": {"abt_io":[{"pool":"__primary__","config":true}]}
-    },
-
-    {
-        "test": "two ABT-IO instances with the same name",
-        "input": {"abt_io":[{"name":"my_abt_io","pool":"__primary__"},{"name":"my_abt_io","pool":"__primary__"}]}
-    },
-
-
-    {
-        "test": "invalid mona section type",
-        "input": {"mona":123}
-    },
-
-    {
-        "test": "invalid mona instance type",
-        "input": {"mona":[123]}
-    },
-
-    {
-        "test": "invalid type for mona instance name",
-        "input": {"mona":[{"name":123}]}
-    },
-
-    {
-        "test": "invalid MoNA instance pool field type",
-        "input": {"mona":[{"pool":true}]}
-    },
-
-    {
-        "test": "two MoNA instances with the same name",
-        "input": {"mona":[{"name":"my_mona","pool":"__primary__"},{"name":"my_mona","pool":"__primary__"}]}
-    },
-
-    {
-        "test": "invalid MoNA instance pool name",
-        "input": {"mona":[{"pool":"abc"}]}
-    },
-
-    {
-        "test": "invalid MoNA instance pool index",
-        "input": {"mona":[{"pool":42}]}
-    },
-
-    {
-        "test": "invalid MoNA instance address type",
-        "input": {"mona":[{"pool":"__primary__","address":123}]}
-    },
-
-    {
-        "test": "invalid MoNA address",
-        "input": {"mona":[{"name":"my_mona","pool":"__primary__","address":"abc"}]}
-    },
-
-
     {
         "test": "invalid type for libraries field",
         "input": {"libraries":123}
@@ -93,12 +6,12 @@
 
     {
         "test": "invalid type for library",
-        "input": {"libraries":{"module_a":123}}
+        "input": {"libraries":[123]}
     },
 
     {
         "test": "library not found",
-        "input": {"libraries":{"module_a":"abc.so"}}
+        "input": {"libraries":["abc.so"]}
     },
 
 
@@ -124,113 +37,47 @@
 
     {
         "test": "invalid type for provider name in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":true,"provider_id":123,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":true,"provider_id":123,"type":"module_a"}]}
     },
 
     {
         "test": "invalid type for provider type in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"type":true}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider","provider_id":123,"type":true}]}
     },
 
     {
         "test": "invalid type for provider_id in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":true,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider","provider_id":true,"type":"module_a"}]}
     },
 
     {
         "test": "invalid provider_id value in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":-1,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider","provider_id":-1,"type":"module_a"}]}
     },
 
     {
         "test": "invalid config type in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"type":"module_a","config":true}]}
-    },
-
-    {
-        "test": "invalid pool type in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"type":"module_a","pool":true}]}
-    },
-
-    {
-        "test": "invalid pool name in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"type":"module_a","pool":"abc"}]}
-    },
-
-    {
-        "test": "invalid pool index in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"type":"module_a","pool":1234}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider","provider_id":123,"type":"module_a","config":true}]}
     },
 
     {
         "test": "provider definition without a name",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"provider_id":123,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"provider_id":123,"type":"module_a"}]}
     },
 
     {
         "test": "empty provider name in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"","provider_id":123,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"","provider_id":123,"type":"module_a"}]}
     },
 
     {
         "test": "two providers with the same name",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider","provider_id":1,"type":"module_a"},{"name":"my_provider","provider_id":2,"type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider","provider_id":1,"type":"module_a"},{"name":"my_provider","provider_id":2,"type":"module_a"}]}
     },
 
     {
         "test": "two providers with the same provider id",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"providers":[{"name":"my_provider1","provider_id":42,"type":"module_a"},{"name":"my_provider2","provider_id":42,"type":"module_a"}]}
-    },
-
-
-    {
-        "test": "invalid type of client field",
-        "input": {"clients":true}
-    },
-
-    {
-        "test": "invalid type of client definition",
-        "input": {"clients":[true]}
-    },
-
-    {
-        "test": "no client type in client definition",
-        "input": {"clients":[{"name":"my_client"}]}
-    },
-
-    {
-        "test": "invalid client type in client definition",
-        "input": {"clients":[{"name":"my_client","type":"module_invalid"}]}
-    },
-
-    {
-        "test": "invalid type for client name in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"name":true,"type":"module_a"}]}
-    },
-
-    {
-        "test": "invalid type for client type in provider definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"name":"my_client","type":true}]}
-    },
-
-    {
-        "test": "invalid config type in client definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"name":"my_client","type":"module_a","config":true}]}
-    },
-
-    {
-        "test": "client definition without a name",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"type":"module_a"}]}
-    },
-
-    {
-        "test": "empty client name in client definition",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"name":"","type":"module_a"}]}
-    },
-
-    {
-        "test": "two clients with the same name",
-        "input": {"libraries":{"module_a":"libModuleA.so"},"clients":[{"name":"my_client","type":"module_a"},{"name":"my_client","type":"module_a"}]}
+        "input": {"libraries":["libModuleA.so"],"providers":[{"name":"my_provider1","provider_id":42,"type":"module_a"},{"name":"my_provider2","provider_id":42,"type":"module_a"}]}
     }
 
 ]
diff --git a/tests/ValidConfigs.json b/tests/ValidConfigs.json
index ef0e65b..f6cfa4e 100644
--- a/tests/ValidConfigs.json
+++ b/tests/ValidConfigs.json
@@ -2,60 +2,24 @@
     {
         "test": "empty configuration",
         "input": {},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[],"ssg":[],"mona":[]}
+        "output": {"bedrock":{"pool":"__primary__","provider_id":0},"libraries":[],"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[]}
     },
 
     {
         "test": "using use_progress_thread and rpc_thread_count in Margo",
         "input": {"margo":{"use_progress_thread":true,"rpc_thread_count":2}},
-        "output": {"abt_io":[],"bedrock":{"pool":"__pool_2__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"},{"access":"mpmc","kind":"fifo_wait","name":"__pool_1__"},{"access":"mpmc","kind":"fifo_wait","name":"__pool_2__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}},{"name":"__xstream_1__","scheduler":{"pools":["__pool_1__"],"type":"basic_wait"}},{"name":"__xstream_2__","scheduler":{"pools":["__pool_2__"],"type":"basic_wait"}},{"name":"__xstream_3__","scheduler":{"pools":["__pool_2__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__pool_1__","progress_timeout_ub_msec":100,"rpc_pool":"__pool_2__"},"providers":[],"ssg":[],"mona":[]}
-    },
-
-    {
-        "test": "defining an ABT-IO instance",
-        "input": {"abt_io":[{"name":"my_abt_io","pool":"__primary__"}]},
-        "output": {"abt_io":[{"config":{"internal_pool_flag":false,"liburing_flags":[],"null_io_read":false,"null_io_write":false,"num_urings":0,"trace_io":false},"name":"my_abt_io","pool":"__primary__"}],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[],"ssg":[],"mona":[]}
-    },
-
-    {
-        "test": "defining an SSG group with mpi bootstrapping",
-        "input": {"ssg":[{"name":"my_ssg_group","bootstrap":"mpi"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[],"ssg":[{"bootstrap":"mpi","credential":-1,"group_file":"","name":"my_ssg_group","pool":"__primary__"}],"mona":[]}
-    },
-
-    {
-        "test": "defining an SSG group with init bootstrapping",
-        "input": {"ssg":[{"name":"my_ssg_group","bootstrap":"init"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[],"ssg":[{"bootstrap":"init","credential":-1,"group_file":"","name":"my_ssg_group","pool":"__primary__"}],"mona":[]}
-    },
-
-    {
-        "test": "defining a Mona instance",
-        "input": {"mona":[{"name":"my_mona", "pool":"__primary__"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"mona":[{"address":"<replaced>","config":{},"name":"my_mona","pool":"__primary__"}],"providers":[],"ssg":[]}
+        "output": {"bedrock":{"pool":"__pool_2__","provider_id":0},"libraries":[],"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"},{"access":"mpmc","kind":"fifo_wait","name":"__pool_1__"},{"access":"mpmc","kind":"fifo_wait","name":"__pool_2__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}},{"name":"__xstream_1__","scheduler":{"pools":["__pool_1__"],"type":"basic_wait"}},{"name":"__xstream_2__","scheduler":{"pools":["__pool_2__"],"type":"basic_wait"}},{"name":"__xstream_3__","scheduler":{"pools":["__pool_2__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__pool_1__","progress_timeout_ub_msec":100,"rpc_pool":"__pool_2__"},"providers":[]}
     },
 
     {
         "test": "load a module library",
-        "input": {"libraries":{"module_a":"./libModuleA.so"}},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{"module_a":"./libModuleA.so"},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"mona":[],"providers":[],"ssg":[]}
+        "input": {"libraries":["./libModuleA.so"]},
+        "output": {"bedrock":{"pool":"__primary__","provider_id":0},"libraries":["./libModuleA.so"],"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[]}
     },
 
     {
         "test": "instantiate a provider from module-a",
-        "input": {"libraries":{"module_a":"./libModuleA.so"},"providers":[{"name":"my_provider","provider_id":123,"tags":[],"type":"module_a"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{"module_a":"./libModuleA.so"},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"mona":[],"providers":[{"config":{},"dependencies":{},"name":"my_provider","pool":"__primary__","provider_id":123,"tags":[],"type":"module_a"}],"ssg":[]}
-    },
-
-    {
-        "test": "instantiate a provider from module-a with simplified syntax",
-        "input": {"libraries.module_a":"./libModuleA.so","providers":[{"name":"my_provider","provider_id":123,"tags":[],"type":"module_a"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[],"libraries":{"module_a":"./libModuleA.so"},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"mona":[],"providers":[{"config":{},"dependencies":{},"name":"my_provider","pool":"__primary__","provider_id":123,"tags":[],"type":"module_a"}],"ssg":[]}
-    },
-
-    {
-        "test": "instantiate a client from module-a",
-        "input": {"libraries":{"module_a":"./libModuleA.so"},"clients":[{"name":"my_client","type":"module_a"}]},
-        "output": {"abt_io":[],"bedrock":{"pool":"__primary__","provider_id":0},"clients":[{"config":{},"dependencies":{},"name":"my_client","tags":[],"type":"module_a"}],"libraries":{"module_a":"./libModuleA.so"},"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"mona":[],"providers":[],"ssg":[]}
+        "input": {"libraries":["./libModuleA.so"],"providers":[{"name":"my_provider","provider_id":123,"tags":[],"type":"module_a"}]},
+        "output": {"bedrock":{"pool":"__primary__","provider_id":0},"libraries":["./libModuleA.so"],"margo":{"argobots":{"abt_mem_max_num_stacks":8,"abt_thread_stacksize":2097152,"lazy_stack_alloc":false,"pools":[{"access":"mpmc","kind":"fifo_wait","name":"__primary__"}],"profiling_dir":".","xstreams":[{"name":"__primary__","scheduler":{"pools":["__primary__"],"type":"basic_wait"}}]},"enable_abt_profiling":false,"handle_cache_size":32,"progress_pool":"__primary__","progress_timeout_ub_msec":100,"rpc_pool":"__primary__"},"providers":[{"config":{},"dependencies":{},"name":"my_provider","provider_id":123,"tags":[],"type":"module_a"}]}
     }
 ]
diff --git a/tests/modules/BaseModule.hpp b/tests/modules/BaseModule.hpp
index 078fb54..a636e58 100644
--- a/tests/modules/BaseModule.hpp
+++ b/tests/modules/BaseModule.hpp
@@ -1,51 +1,28 @@
 #include "Helpers.hpp"
 #include <iostream>
 
-class BaseServiceFactory : public bedrock::AbstractServiceFactory {
+class BaseComponent : public bedrock::AbstractComponent {
 
-    public:
-
-    BaseServiceFactory() = default;
-
-    virtual ~BaseServiceFactory() = default;
-
-    void* registerProvider(const bedrock::FactoryArgs& args) override {
-        return new TestProvider(args);
-    }
-
-    void deregisterProvider(void* provider) override {
-        delete static_cast<TestProvider*>(provider);
-    }
-
-    std::string getProviderConfig(void* provider) override {
-        return static_cast<TestProvider*>(provider)->config;
-    }
+    std::unique_ptr<TestProvider> m_provider;
 
-    void* initClient(const bedrock::FactoryArgs& args) override {
-        return new TestClient(args);
-    }
-
-    void finalizeClient(void* client) override {
-        delete static_cast<TestClient*>(client);
-    }
+    public:
 
-    std::string getClientConfig(void* client) override {
-        return static_cast<TestClient*>(client)->config;
-    }
+    BaseComponent(const bedrock::ComponentArgs& args)
+    : m_provider{std::make_unique<TestProvider>(args)} {}
 
-    void* createProviderHandle(void* client, hg_addr_t address, uint16_t provider_id) override {
-        return new TestProviderHandle(client, address, provider_id);
-    }
+    virtual ~BaseComponent() = default;
 
-    void destroyProviderHandle(void* providerHandle) override {
-        delete static_cast<TestProviderHandle*>(providerHandle);
+    void* getHandle() override {
+        return static_cast<void*>(m_provider.get());
     }
 
-    std::vector<bedrock::Dependency> getProviderDependencies(const char*) override {
-        return {};
-    }
+    static std::shared_ptr<bedrock::AbstractComponent>
+        Register(const bedrock::ComponentArgs& args) {
+            return std::make_shared<BaseComponent>(args);
+        }
 
-    std::vector<bedrock::Dependency> getClientDependencies(const char*) override {
-        return {};
-    }
+    static std::vector<bedrock::Dependency>
+        GetDependencies(const bedrock::ComponentArgs& args) {
+            return std::vector<bedrock::Dependency>{};
+        }
 };
diff --git a/tests/modules/Helpers.hpp b/tests/modules/Helpers.hpp
index d9878ab..fcf429e 100644
--- a/tests/modules/Helpers.hpp
+++ b/tests/modules/Helpers.hpp
@@ -3,74 +3,29 @@
 
 #include <string>
 #include <vector>
-#include <bedrock/AbstractServiceFactory.hpp>
+#include <bedrock/AbstractComponent.hpp>
 
 struct TestProvider {
 
-    std::string                            name;
-    margo_instance_id                      mid;
-    uint16_t                               provider_id;
-    ABT_pool                               pool;
-    std::string                            config;
-    std::unordered_map<std::string, std::vector<void*>> dependencies;
+    std::string                          name;
+    thallium::engine                     engine;
+    uint16_t                             provider_id;
+    std::string                          config;
+    std::unordered_map<
+        std::string, std::vector<std::string>> dependencies;
 
-    TestProvider(const bedrock::FactoryArgs& args)
+    TestProvider(const bedrock::ComponentArgs& args)
     : name(args.name)
-    , mid(args.mid)
+    , engine(args.engine)
     , provider_id(args.provider_id)
-    , pool(args.pool)
     , config(args.config)
     {
         for(const auto& dep : args.dependencies) {
-            for(const auto& a : dep.second.dependencies) {
-                dependencies[dep.first].push_back(a->getHandle<void*>());
+            for(const auto& a : dep.second) {
+                dependencies[dep.first].push_back(a->getName());
             }
         }
     }
 };
 
-struct TestClient {
-
-    std::string                            name;
-    margo_instance_id                      mid;
-    uint16_t                               provider_id;
-    ABT_pool                               pool;
-    std::string                            config;
-    std::unordered_map<std::string, std::vector<void*>> dependencies;
-
-    TestClient(const bedrock::FactoryArgs& args)
-    : name(args.name)
-    , mid(args.mid)
-    , provider_id(args.provider_id)
-    , pool(args.pool)
-    , config(args.config)
-    {
-        for(const auto& dep : args.dependencies) {
-            for(const auto& a : dep.second.dependencies) {
-                dependencies[dep.first].push_back(a->getHandle<void*>());
-            }
-        }
-    }
-};
-
-struct TestProviderHandle {
-
-    TestClient* client;
-    hg_addr_t   addr = HG_ADDR_NULL;
-    uint16_t    provider_id;
-
-    TestProviderHandle(void* c, hg_addr_t a, uint16_t pr_id)
-    : client(static_cast<TestClient*>(c))
-    , provider_id(pr_id)
-    {
-        margo_addr_dup(client->mid, a, &addr);
-    }
-
-    ~TestProviderHandle()
-    {
-        margo_addr_free(client->mid, addr);
-    }
-
-};
-
 #endif
diff --git a/tests/modules/ModuleA.cpp b/tests/modules/ModuleA.cpp
index 89450ec..ee3ab20 100644
--- a/tests/modules/ModuleA.cpp
+++ b/tests/modules/ModuleA.cpp
@@ -1,7 +1,11 @@
 #include "BaseModule.hpp"
 
-class ModuleAServiceFactory : public BaseServiceFactory {
+class ComponentA : public BaseComponent {
 
+    public:
+
+    using BaseComponent::Register;
+    using BaseComponent::GetDependencies;
 };
 
-BEDROCK_REGISTER_MODULE_FACTORY(module_a, ModuleAServiceFactory)
+BEDROCK_REGISTER_COMPONENT_TYPE(module_a, ComponentA)
diff --git a/tests/modules/ModuleB.cpp b/tests/modules/ModuleB.cpp
index d3af54d..df91a8b 100644
--- a/tests/modules/ModuleB.cpp
+++ b/tests/modules/ModuleB.cpp
@@ -1,7 +1,11 @@
 #include "BaseModule.hpp"
 
-class ModuleBServiceFactory : public BaseServiceFactory {
+class ComponentB : public BaseComponent {
 
+    public:
+
+    using BaseComponent::Register;
+    using BaseComponent::GetDependencies;
 };
 
-BEDROCK_REGISTER_MODULE_FACTORY(module_b, ModuleBServiceFactory)
+BEDROCK_REGISTER_COMPONENT_TYPE(module_b, ComponentB)
diff --git a/tests/modules/ModuleC.cpp b/tests/modules/ModuleC.cpp
index 0796604..f399adf 100644
--- a/tests/modules/ModuleC.cpp
+++ b/tests/modules/ModuleC.cpp
@@ -1,9 +1,37 @@
-#include "BaseModule.hpp"
+#include "Helpers.hpp"
 #include <nlohmann/json.hpp>
 
-using json = nlohmann::json;
+class ComponentC : public bedrock::AbstractComponent {
 
-class ModuleCServiceFactory : public BaseServiceFactory {
+    using json = nlohmann::json;
+
+    std::unique_ptr<TestProvider> m_provider;
+
+    public:
+
+    ComponentC(const bedrock::ComponentArgs& args)
+    : m_provider{std::make_unique<TestProvider>(args)} {}
+
+    void* getHandle() override {
+        return static_cast<void*>(m_provider.get());
+    }
+
+    static std::shared_ptr<bedrock::AbstractComponent>
+        Register(const bedrock::ComponentArgs& args) {
+            return std::make_shared<ComponentC>(args);
+        }
+
+    static std::vector<bedrock::Dependency>
+        GetDependencies(const bedrock::ComponentArgs& args) {
+            auto config = json::parse(args.config);
+            std::vector<bedrock::Dependency> dependencies;
+            if(!config.contains("expected_provider_dependencies"))
+                return dependencies;
+            auto& expected_dependencies = config["expected_provider_dependencies"];
+            return extractDependencies(expected_dependencies);
+        }
+
+    private:
 
     static inline std::vector<bedrock::Dependency> extractDependencies(
             const json& expected_dependencies) {
@@ -14,48 +42,17 @@ class ModuleCServiceFactory : public BaseServiceFactory {
             if(!dep.is_object()) continue;
             if(!dep.contains("name") || !dep.contains("type"))
                 continue;
-            int32_t flags = 0;
-            auto name = dep["name"].get<std::string>();
-            auto type = dep["type"].get<std::string>();
-            auto kind = dep.value("kind", std::string{""});
-            if(kind == "client") {
-                flags |= BEDROCK_KIND_CLIENT;
-            }
-            if(kind == "provider") {
-                flags |= BEDROCK_KIND_PROVIDER;
-            }
-            if(kind == "provider_handle") {
-                flags |= BEDROCK_KIND_PROVIDER_HANDLE;
-            }
-            if(dep.value("is_required", false)) {
-                flags |= BEDROCK_REQUIRED;
-            }
-            if(dep.value("is_array", false)) {
-                flags |= BEDROCK_ARRAY;
-            }
-            dependencies.push_back(bedrock::Dependency{name, type, flags});
+            bedrock::Dependency the_dep;
+            the_dep.name = dep["name"].get<std::string>();
+            the_dep.type = dep["type"].get<std::string>();
+            the_dep.is_array = dep.value("is_array", false);
+            the_dep.is_required = dep.value("is_required", false);
+            the_dep.is_updatable = dep.value("is_updatable", false);
+            dependencies.push_back(the_dep);
         }
         return dependencies;
     }
 
-    std::vector<bedrock::Dependency> getProviderDependencies(const char* cfg) override {
-        auto config = json::parse(cfg);
-        std::vector<bedrock::Dependency> dependencies;
-        if(!config.contains("expected_provider_dependencies"))
-            return dependencies;
-        auto& expected_dependencies = config["expected_provider_dependencies"];
-        return extractDependencies(expected_dependencies);
-    }
-
-    std::vector<bedrock::Dependency> getClientDependencies(const char* cfg) override {
-        auto config = json::parse(cfg);
-        std::vector<bedrock::Dependency> dependencies;
-        if(!config.contains("expected_client_dependencies"))
-            return dependencies;
-        auto& expected_dependencies = config["expected_client_dependencies"];
-        return extractDependencies(expected_dependencies);
-    }
-
 };
 
-BEDROCK_REGISTER_MODULE_FACTORY(module_c, ModuleCServiceFactory)
+BEDROCK_REGISTER_COMPONENT_TYPE(module_c, ComponentC)
diff --git a/tests/spack.yaml b/tests/spack.yaml
index 982dc7e..dfe7058 100644
--- a/tests/spack.yaml
+++ b/tests/spack.yaml
@@ -11,9 +11,6 @@ spack:
   - mochi-margo
   - mochi-thallium
   - mochi-bedrock-module-api
-  - mochi-abt-io
-  - mochi-ssg+mpi
-  - mochi-mona
   - mochi-flock
   - nlohmann-json
   - nlohmann-json-schema-validator
@@ -28,7 +25,6 @@ spack:
   - py-coverage
   - py-typer
   - py-rich
-  - py-mochi-ssg
   - py-configspace
   concretizer:
     unify: true