diff --git a/test/agent/Makefile.am b/test/agent/Makefile.am
index 13687960893a6ff48eda255df745322681c9b406..34aae1f329efd2d5ef9ef7e89395cd6454a4a70a 100644
--- a/test/agent/Makefile.am
+++ b/test/agent/Makefile.am
@@ -1,15 +1,14 @@
 include $(top_srcdir)/globals.mk
 
-AM_CXXFLAGS += -I$(top_srcdir)/src -I.. $(GUILE_CFLAGS) -rdynamic
+AM_CXXFLAGS += -I$(top_srcdir)/src -I./src $(GUILE_CFLAGS) -rdynamic
 AM_LDFLAGS  += $(GUILE_LIBS)
 
 noinst_PROGRAMS = agent
 
-agent_SOURCES =      \
-	agent.cpp    \
-	agent.h      \
-	bindings.cpp \
-	bindings.h   \
-	main.cpp
+agent_SOURCES =                   \
+	src/main.cpp              \
+	src/utils.h               \
+	src/bindings/bindings.cpp \
+	src/bindings/bindings.h
 
 agent_LDADD = $(top_builddir)/src/libring.la
diff --git a/test/agent/agent.cpp b/test/agent/agent.cpp
deleted file mode 100644
index 44ffdb2bcb4d64ffb24f86031022aa23fdb2162c..0000000000000000000000000000000000000000
--- a/test/agent/agent.cpp
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- *  Copyright (C) 2021 Savoir-faire Linux Inc.
- *
- *  Author: Olivier Dion <olivier.dion@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
- */
-
-/* std */
-#include <atomic>
-#include <chrono>
-#include <condition_variable>
-#include <fstream>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-/* DRing */
-#include "account_const.h"
-#include "jami/presencemanager_interface.h"
-#include "jami/callmanager_interface.h"
-#include "jami/configurationmanager_interface.h"
-#include "jami/conversation_interface.h"
-
-/* agent */
-#include "agent/agent.h"
-
-using usize = size_t;
-
-#define LOG_AGENT_STATE() AGENT_DBG("In state %s", __FUNCTION__)
-
-void
-Agent::searchForPeers(std::vector<std::string>& peers)
-{
-    LOG_AGENT_STATE();
-
-    for (auto it = peers.begin(); it != peers.end(); ++it) {
-        AGENT_INFO("Searching for peer %s", it->c_str());
-
-        DRing::sendTrustRequest(accountID_, it->c_str());
-        DRing::subscribeBuddy(accountID_, it->c_str(), true);
-    }
-}
-
-bool
-Agent::ping(const std::string& conversation)
-{
-    LOG_AGENT_STATE();
-
-    std::mutex mtx;
-    std::condition_variable cv;
-    std::atomic<bool> pongReceived(false);
-
-    std::string alphabet = "0123456789ABCDEF";
-    std::string messageSent;
-
-    for (usize i = 0; i < 16; ++i) {
-        messageSent.push_back(alphabet[rand() % alphabet.size()]);
-    }
-
-    onMessageReceived_.add([&](const std::string& /* accountID */,
-                               const std::string& conversationID,
-                               std::map<std::string, std::string> message) {
-        if ("text/plain" != message.at("type")) {
-            return true;
-        }
-
-        auto msg = message.at("body");
-
-        if (pongReceived.load()) {
-            return false;
-        }
-
-        if (conversationID == conversation and message.at("author") != peerID_
-            and msg == "PONG:" + messageSent) {
-            std::unique_lock lk(mtx);
-            pongReceived.store(true);
-            cv.notify_one();
-            return false;
-        }
-
-        return true;
-    });
-
-    AGENT_INFO("Sending ping `%s` to `%s`", messageSent.c_str(), conversation.c_str());
-
-    DRing::sendMessage(accountID_, conversation, messageSent, "");
-
-    /* Waiting for echo */
-
-    std::unique_lock<std::mutex> lk(mtx);
-
-    bool ret = (std::cv_status::no_timeout == cv.wait_for(lk, std::chrono::seconds(30))
-                and pongReceived.load());
-
-    AGENT_INFO("Pong %s", ret ? "received" : "missing");
-
-    return ret;
-}
-
-std::string
-Agent::someContact() const
-{
-    auto members = DRing::getConversationMembers(accountID_, someConversation());
-
-    for (const auto& member : members) {
-        if (member.at("uri") != peerID_) {
-            return member.at("uri");
-        }
-    }
-
-    return "";
-}
-
-std::string
-Agent::someConversation() const
-{
-    if (conversations_.empty()) {
-        return "";
-    }
-
-    auto it = conversations_.begin();
-
-    std::advance(it, rand() % conversations_.size());
-
-    return *it;
-}
-
-bool
-Agent::placeCall(const std::string& contact)
-{
-    LOG_AGENT_STATE();
-
-    std::mutex mtx;
-    std::condition_variable cv;
-    bool success(false);
-    bool over(false);
-
-    std::string callID = "";
-
-    onCallStateChanged_.add([&](const std::string& call_id, const std::string& state, signed code) {
-        AGENT_INFO("[call:%s] In state %s : %d", callID.c_str(), state.c_str(), code);
-
-        std::unique_lock lk(mtx);
-
-        if (call_id != callID) {
-            return true;
-        }
-
-        bool ret = true;
-
-        if ("CURRENT" == state) {
-            success = true;
-        } else if ("OVER" == state) {
-            over = true;
-            ret = false;
-        }
-
-        cv.notify_one();
-
-        return ret;
-    });
-
-    callID = DRing::placeCall(accountID_, contact);
-
-    AGENT_INFO("Waiting for call %s", callID.c_str());
-
-    /* TODO - Parametize me */
-    {
-        std::unique_lock lk(mtx);
-        cv.wait_for(lk, std::chrono::seconds(30), [&] { return success or over; });
-    }
-
-    if (success) {
-        AGENT_INFO("[call:%s] to %s: SUCCESS", callID.c_str(), contact.c_str());
-        DRing::hangUp(callID);
-    } else {
-        AGENT_INFO("[call:%s] to %s: FAIL", callID.c_str(), contact.c_str());
-    }
-
-    if (not over) {
-        std::unique_lock lk(mtx);
-        cv.wait_for(lk, std::chrono::seconds(30), [&] { return over; });
-    }
-
-    return success;
-}
-
-void
-Agent::wait(std::chrono::seconds period)
-{
-    LOG_AGENT_STATE();
-
-    std::this_thread::sleep_for(period);
-}
-
-void
-Agent::setDetails(const std::map<std::string, std::string>& details)
-{
-    LOG_AGENT_STATE();
-
-    auto cv = std::make_shared<std::condition_variable>();
-    auto cont = std::make_shared<std::atomic<bool>>(true);
-
-    std::string info("Setting details:\n");
-
-    for (const auto& [key, value] : details) {
-        info += key + " = " + value + "\n";
-    }
-
-    AGENT_INFO("%s", info.c_str());
-
-    DRing::setAccountDetails(accountID_, details);
-}
-
-std::map<std::string, std::string>
-Agent::getDetails() const
-{
-    return DRing::getAccountDetails(accountID_);
-}
-
-void
-Agent::activate(bool enable)
-{
-    LOG_AGENT_STATE();
-
-    DRing::sendRegister(accountID_, enable);
-
-    if (enable) {
-        waitForAnnouncement();
-    }
-}
-
-void
-Agent::exportToArchive(const std::string& path)
-{
-    LOG_AGENT_STATE();
-
-    AGENT_ASSERT(DRing::exportToFile(accountID_, path),
-                 "Failed to export account to `%s`",
-                 path.c_str());
-}
-
-void
-Agent::importFromArchive(const std::string& path)
-{
-    LOG_AGENT_STATE();
-
-    std::map<std::string, std::string> details;
-
-    details[DRing::Account::ConfProperties::TYPE] = "RING";
-    details[DRing::Account::ConfProperties::DISPLAYNAME] = "AGENT";
-    details[DRing::Account::ConfProperties::ALIAS] = "AGENT";
-    details[DRing::Account::ConfProperties::ARCHIVE_PASSWORD] = "";
-    details[DRing::Account::ConfProperties::ARCHIVE_PIN] = "";
-    details[DRing::Account::ConfProperties::ARCHIVE_PATH] = path;
-
-    AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID");
-
-    details = DRing::getAccountDetails(accountID_);
-
-    waitForAnnouncement();
-
-    AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME),
-                 "Bad display name");
-
-    peerID_ = details.at(DRing::Account::ConfProperties::USERNAME);
-    conversations_ = DRing::getConversations(accountID_);
-
-    AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str());
-}
-
-void
-Agent::ensureAccount()
-{
-    LOG_AGENT_STATE();
-
-    std::map<std::string, std::string> details;
-
-    details = DRing::getAccountDetails(accountID_);
-
-    if (details.empty()) {
-        details[DRing::Account::ConfProperties::TYPE] = "RING";
-        details[DRing::Account::ConfProperties::DISPLAYNAME] = "AGENT";
-        details[DRing::Account::ConfProperties::ALIAS] = "AGENT";
-        details[DRing::Account::ConfProperties::ARCHIVE_PASSWORD] = "";
-        details[DRing::Account::ConfProperties::ARCHIVE_PIN] = "";
-        details[DRing::Account::ConfProperties::ARCHIVE_PATH] = "";
-
-        AGENT_ASSERT(accountID_ == DRing::addAccount(details, accountID_), "Bad accountID");
-    }
-
-    waitForAnnouncement();
-
-    details = DRing::getAccountDetails(accountID_);
-
-    AGENT_ASSERT("AGENT" == details.at(DRing::Account::ConfProperties::DISPLAYNAME),
-                 "Bad display name");
-
-    peerID_ = details.at(DRing::Account::ConfProperties::USERNAME);
-    conversations_ = DRing::getConversations(accountID_);
-
-    AGENT_INFO("Using account %s - %s", accountID_.c_str(), peerID_.c_str());
-}
-
-void
-Agent::waitForCallState(const std::string& wanted)
-{
-    LOG_AGENT_STATE();
-
-    std::mutex mtx;
-    std::condition_variable cv;
-    std::unique_lock lk(mtx);
-
-    onCallStateChanged_.add(
-        [&](const std::string& /* call_id */, const std::string& state, signed /* code */) {
-            if (wanted == state) {
-                std::unique_lock lk(mtx);
-                cv.notify_one();
-
-                return false;
-            }
-
-            return true;
-        });
-
-    cv.wait(lk);
-}
-
-Agent&
-Agent::instance()
-{
-    static Agent agent;
-
-    return agent;
-}
-
-void
-Agent::installSignalHandlers()
-{
-    using namespace std::placeholders;
-    using std::bind;
-
-    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> handlers;
-
-    handlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCallWithMedia>(
-        bind(&Agent::Handler<const std::string&,
-                             const std::string&,
-                             const std::string&,
-                             const std::vector<DRing::MediaMap>>::execute,
-             &onIncomingCall_,
-             _1,
-             _2,
-             _3,
-             _4)));
-
-    handlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>(
-        bind(&Agent::Handler<const std::string&, const std::string&, signed>::execute,
-             &onCallStateChanged_,
-             _1,
-             _2,
-             _3)));
-
-    handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
-        bind(&Agent::Handler<const std::string&,
-                             const std::string&,
-                             std::map<std::string, std::string>>::execute,
-             &onMessageReceived_,
-             _1,
-             _2,
-             _3)));
-
-    handlers.insert(
-        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
-            bind(&Agent::Handler<const std::string&,
-                                 const std::string&,
-                                 std::map<std::string, std::string>>::execute,
-                 &onConversationRequestReceived_,
-                 _1,
-                 _2,
-                 _3)));
-
-    handlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
-        bind(&Agent::Handler<const std::string&, const std::string&>::execute,
-             &onConversationReady_,
-             _1,
-             _2)));
-
-    handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::ContactAdded>(
-        bind(&Agent::Handler<const std::string&, const std::string&, bool>::execute,
-             &onContactAdded_,
-             _1,
-             _2,
-             _3)));
-
-    handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::RegistrationStateChanged>(
-        bind(&Agent::Handler<const std::string&, const std::string&, int, const std::string&>::execute,
-             &onRegistrationStateChanged_,
-             _1,
-             _2,
-             _3,
-             _4)));
-
-    handlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
-        bind(&Agent::Handler<const std::string&, const std::map<std::string, std::string>&>::execute,
-             &onVolatileDetailsChanged_,
-             _1,
-             _2)));
-
-    DRing::registerSignalHandlers(handlers);
-}
-
-void
-Agent::registerStaticCallbacks()
-{
-    onIncomingCall_.add([=](const std::string& accountID,
-                            const std::string& callID,
-                            const std::string& peerDisplayName,
-                            const std::vector<DRing::MediaMap> mediaList) {
-        (void) accountID;
-        (void) peerDisplayName;
-
-        std::string medias("");
-
-        for (const auto& media : mediaList) {
-            for (const auto& [key, value] : media) {
-                medias += key + "=" + value + "\n";
-            }
-        }
-
-        AGENT_INFO("Incoming call `%s` from `%s` with medias:\n`%s`",
-                   callID.c_str(),
-                   peerDisplayName.c_str(),
-                   medias.c_str());
-
-        AGENT_ASSERT(DRing::acceptWithMedia(callID, mediaList),
-                     "Failed to accept call `%s`",
-                     callID.c_str());
-
-        return true;
-    });
-
-    onMessageReceived_.add([=](const std::string& accountID,
-                               const std::string& conversationID,
-                               std::map<std::string, std::string> message) {
-        (void) accountID;
-
-        /* Read only text message */
-        if ("text/plain" != message.at("type")) {
-            return true;
-        }
-
-        auto author = message.at("author");
-
-        /* Skip if sent by agent */
-        if (peerID_ == author) {
-            return true;
-        }
-
-        auto msg = message.at("body");
-
-        AGENT_INFO("Incomming message `%s` from %s", msg.c_str(), author.c_str());
-
-        /* Echo back */
-        if (0 != msg.rfind("PONG:", 0)) {
-            DRing::sendMessage(accountID_, conversationID, "PONG:" + msg, "");
-        }
-
-        return true;
-    });
-
-    onConversationRequestReceived_.add([=](const std::string& accountID,
-                                           const std::string& conversationID,
-                                           std::map<std::string, std::string> meta) {
-        (void) meta;
-
-        AGENT_INFO("Conversation request received for account %s", accountID.c_str());
-
-        DRing::acceptConversationRequest(accountID, conversationID);
-
-        return true;
-    });
-
-    onConversationReady_.add([=](const std::string& accountID, const std::string& conversationID) {
-        (void) accountID;
-        conversations_.emplace_back(conversationID);
-        return true;
-    });
-
-    onContactAdded_.add([=](const std::string& accountID, const std::string& URI, bool confirmed) {
-        AGENT_INFO("Contact added `%s` : %s", URI.c_str(), confirmed ? "accepted" : "refused");
-        if (confirmed) {
-            DRing::subscribeBuddy(accountID, URI, true);
-        }
-        return true;
-    });
-}
-
-void
-Agent::waitForAnnouncement(std::chrono::seconds timeout)
-{
-    LOG_AGENT_STATE();
-
-    std::condition_variable cv;
-
-    std::mutex mtx;
-
-    onVolatileDetailsChanged_.add(
-        [&](const std::string& accountID, const std::map<std::string, std::string>& details) {
-            if (accountID_ != accountID) {
-                return true;
-            }
-
-            try {
-                if ("true" != details.at(DRing::Account::VolatileProperties::DEVICE_ANNOUNCED)) {
-                    return true;
-                }
-            } catch (const std::out_of_range&) {
-                return true;
-            }
-
-            std::unique_lock lk(mtx);
-
-            cv.notify_one();
-
-            return false;
-        });
-
-    std::unique_lock lk(mtx);
-
-    AGENT_ASSERT(std::cv_status::no_timeout == cv.wait_for(lk, timeout),
-                 "Timeout while waiting for account announcement on DHT");
-}
-
-void
-Agent::init()
-{
-    DRing::logging("console", "on");
-    LOG_AGENT_STATE();
-
-    installSignalHandlers();
-    registerStaticCallbacks();
-}
-
-void
-Agent::fini()
-{
-    LOG_AGENT_STATE();
-
-    DRing::unregisterSignalHandlers();
-}
diff --git a/test/agent/agent.h b/test/agent/agent.h
deleted file mode 100644
index 16e1ffbd693abb17857337a1f6360b6ede4d8501..0000000000000000000000000000000000000000
--- a/test/agent/agent.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- *  Copyright (C) 2021 Savoir-faire Linux Inc.
- *
- *  Author: Olivier Dion <olivier.dion@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
- */
-
-#pragma once
-
-/* Dring */
-#include "logger.h"
-#include "jami/jami.h"
-
-/* std */
-#include <chrono>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <vector>
-
-#define AGENT_ERR(FMT, ARGS...)  JAMI_ERR("AGENT: " FMT, ##ARGS)
-#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS)
-#define AGENT_DBG(FMT, ARGS...)  JAMI_DBG("AGENT: " FMT, ##ARGS)
-#define AGENT_ASSERT(COND, MSG, ARGS...) \
-    if (not(COND)) { \
-        AGENT_ERR(MSG, ##ARGS); \
-        exit(1); \
-    }
-
-class Agent
-{
-    template<typename... Args>
-    class Handler
-    {
-        std::mutex mutex_;
-        std::vector<std::function<bool(Args...)>> callbacks_;
-
-    public:
-        void add(std::function<bool(Args...)>&& cb)
-        {
-            std::unique_lock<std::mutex> lck(mutex_);
-            callbacks_.emplace_back(std::move(cb));
-        }
-
-        void execute(Args... args)
-        {
-            std::vector<std::function<bool(Args...)>> to_keep;
-            std::unique_lock<std::mutex> lck(mutex_);
-
-            for (auto& cb : callbacks_) {
-                if (cb(args...)) {
-                    to_keep.emplace_back(std::move(cb));
-                }
-            }
-
-            callbacks_.swap(to_keep);
-        }
-    };
-
-    /* Signal handlers */
-    Handler<const std::string&, const std::string&, std::map<std::string, std::string>>
-        onMessageReceived_;
-
-    Handler<const std::string&, const std::string&, std::map<std::string, std::string>>
-        onConversationRequestReceived_;
-
-    Handler<const std::string&, const std::string&> onConversationReady_;
-
-    Handler<const std::string&, const std::string&, signed> onCallStateChanged_;
-
-    Handler<const std::string&,
-            const std::string&,
-            const std::string&,
-            const std::vector<DRing::MediaMap>>
-        onIncomingCall_;
-
-    Handler<const std::string&, const std::string&, bool> onContactAdded_;
-
-    Handler<const std::string&, const std::string&, int, const std::string&>
-        onRegistrationStateChanged_;
-
-    Handler<const std::string&, const std::map<std::string, std::string>&>
-    onVolatileDetailsChanged_;
-
-    /*  Initialize agent */
-    void installSignalHandlers();
-    void registerStaticCallbacks();
-
-    /* Bookkeeping */
-    std::string peerID_;
-    const std::string accountID_ {"afafafafafafafaf"};
-    std::vector<std::string> conversations_;
-
-public:
-    /* Behavior */
-    bool ping(const std::string& conversation);
-    bool placeCall(const std::string& contact);
-    std::string someContact() const;
-    std::string someConversation() const;
-    void setDetails(const std::map<std::string, std::string>& details);
-    std::map<std::string, std::string> getDetails() const;
-    void stopRecording(const std::string& context);
-    void startRecording(const std::string& context, const std::string& to);
-    void searchForPeers(std::vector<std::string>& peers);
-    void wait(std::chrono::seconds period);
-    void exportToArchive(const std::string& path);
-    void importFromArchive(const std::string& path);
-    void ensureAccount();
-    void waitForAnnouncement(std::chrono::seconds timeout=std::chrono::seconds(30));
-    void activate(bool state);
-    void waitForCallState(const std::string& wanted="CURRENT");
-
-    void init();
-    void fini();
-
-    static Agent& instance();
-};
diff --git a/test/agent/bindings.cpp b/test/agent/bindings.cpp
deleted file mode 100644
index e00b5efa412437361f49505567891217eff6291e..0000000000000000000000000000000000000000
--- a/test/agent/bindings.cpp
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- *  Copyright (C) 2021 Savoir-faire Linux Inc.
- *
- *  Author: Olivier Dion <olivier.dion@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
- */
-
-#include "agent/agent.h"
-#include "agent/bindings.h"
-
-static inline SCM
-to_guile(bool b)
-{
-    return scm_from_bool(b);
-}
-
-static inline SCM
-to_guile(const std::string& str)
-{
-    return scm_from_utf8_string(str.c_str());
-}
-
-template<typename T>
-static inline SCM
-to_guile(const std::vector<T>& values)
-{
-    SCM vec = scm_make_vector(values.size(), SCM_UNDEFINED);
-
-    for (size_t i = 0; i < values.size(); ++i) {
-        SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i]));
-    }
-
-    return vec;
-}
-
-template<typename K, typename V>
-static inline SCM
-to_guile(const std::map<K, V>& map)
-{
-    SCM assoc = SCM_EOL;
-
-    for (auto const& [key, value] : map) {
-        SCM pair = scm_cons(to_guile(key), to_guile(value));
-        assoc = scm_cons(pair, assoc);
-    }
-
-    return assoc;
-}
-
-template<typename... Args>
-static inline SCM
-pack_to_guile(Args... args)
-{
-    SCM lst = SCM_EOL;
-    std::vector<SCM> values = {to_guile(args)...};
-
-    while (values.size()) {
-        lst = scm_cons(values.back(), lst);
-        values.pop_back();
-    }
-
-    return lst;
-}
-
-template<typename... Args>
-static inline SCM
-apply_to_guile(SCM body_proc, Args... args)
-{
-    AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)),
-                 "body_proc must be a procedure");
-
-    SCM arglst = pack_to_guile(args...);
-
-    return scm_apply_0(body_proc, arglst);
-}
-
-struct from_guile
-{
-    SCM value;
-
-    from_guile(SCM val)
-        : value(val)
-    {}
-
-    operator bool()
-    {
-        AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool");
-
-        return scm_to_bool(value);
-    }
-
-    operator std::string()
-    {
-        AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string");
-
-        char* str_raw = scm_to_locale_string(value);
-        std::string ret(str_raw);
-        free(str_raw);
-
-        return ret;
-    }
-
-    template<typename T>
-    operator std::vector<T>()
-    {
-        AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector");
-
-        std::vector<T> ret;
-
-        for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) {
-            SCM val = SCM_SIMPLE_VECTOR_REF(value, i);
-
-            ret.emplace_back(from_guile(val));
-        }
-
-        return ret;
-    }
-
-    template<typename K, typename V>
-    operator std::map<K, V>()
-    {
-        AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list");
-
-        std::map<K, V> ret;
-
-        while (not scm_is_null(value)) {
-            SCM pair = scm_car(value);
-
-            K key = from_guile(scm_car(pair));
-            V val = from_guile(scm_cdr(pair));
-
-            ret[key] = val;
-
-            value = scm_cdr(value);
-        }
-
-        return ret;
-    }
-};
-
-static SCM
-wait_binding(SCM period_long_optional)
-{
-    std::chrono::seconds period;
-
-    if (SCM_UNBNDP(period_long_optional)) {
-        period = std::chrono::seconds::max();
-    } else {
-        AGENT_ASSERT(scm_is_number(period_long_optional), "Invalid period");
-        period = std::chrono::seconds(scm_to_uint(period_long_optional));
-    }
-
-    Agent::instance().wait(period);
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-place_call_binding(SCM contact_str)
-{
-    return to_guile(Agent::instance().placeCall(from_guile(contact_str)));
-}
-
-static SCM
-some_conversation_binding()
-{
-    return to_guile(Agent::instance().someConversation());
-}
-
-static SCM
-some_contact_binding()
-{
-    return to_guile(Agent::instance().someContact());
-}
-
-static SCM
-search_peer_binding(SCM peers_vector_or_str)
-{
-    std::vector<std::string> peers;
-
-    if (scm_is_string(peers_vector_or_str)) {
-        peers.emplace_back(from_guile(peers_vector_or_str));
-    } else {
-        peers = from_guile(peers_vector_or_str);
-    }
-
-    Agent::instance().searchForPeers(peers);
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-ping_binding(SCM contact_str)
-{
-    return to_guile(Agent::instance().ping(from_guile(contact_str)));
-}
-
-static SCM
-set_details_binding(SCM details_alist)
-{
-    Agent::instance().setDetails(from_guile(details_alist));
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-get_details_binding()
-{
-    return to_guile(Agent::instance().getDetails());
-}
-
-static SCM
-ensure_account_binding()
-{
-    Agent::instance().ensureAccount();
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-export_to_archive_binding(SCM path)
-{
-    Agent::instance().exportToArchive(from_guile(path));
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-import_from_archive_binding(SCM path)
-{
-    Agent::instance().importFromArchive(from_guile(path));
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-enable_binding()
-{
-    Agent::instance().activate(true);
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-disable_binding()
-{
-    Agent::instance().activate(false);
-
-    return SCM_UNDEFINED;
-}
-
-static SCM
-wait_for_call_state(SCM wanted_state_str)
-{
-    Agent::instance().waitForCallState(from_guile(wanted_state_str));
-
-    return SCM_UNDEFINED;
-}
-
-/*
- * Register Guile bindings here.
- *
- * 1. Name of the binding
- * 2. Number of required argument to binding
- * 3. Number of optional argument to binding
- * 4. Number of rest argument to binding
- * 5. Pointer to C function to call
- *
- * See info guile:
- *
- * Function: SCM scm_c_define_gsubr(const char *name, int req, int opt, int rst, fcn):
- *
- * Register a C procedure FCN as a “subr” — a primitive subroutine that can be
- * called from Scheme.  It will be associated with the given NAME and bind it in
- * the "current environment".  The arguments REQ, OPT and RST specify the number
- * of required, optional and “rest” arguments respectively.  The total number of
- * these arguments should match the actual number of arguments to FCN, but may
- * not exceed 10.  The number of rest arguments should be 0 or 1.
- * ‘scm_c_make_gsubr’ returns a value of type ‘SCM’ which is a “handle” for the
- * procedure.
- */
-void
-install_scheme_primitives()
-{
-    auto define_primitive = [](const char* name, int req, int opt, int rst, void* func) {
-        AGENT_ASSERT(req + opt + rst <= 10, "Primitive binding `%s` has too many argument", name);
-
-        AGENT_ASSERT(0 == rst or 1 == rst, "Rest argument for binding `%s` must be 0 or 1", name);
-
-        scm_c_define_gsubr(name, req, opt, rst, func);
-    };
-
-    define_primitive("agent:search-for-peers", 1, 0, 0, (void*) search_peer_binding);
-    define_primitive("agent:place-call", 1, 0, 0, (void*) place_call_binding);
-    define_primitive("agent:some-contact", 0, 0, 0, (void*) some_contact_binding);
-    define_primitive("agent:some-conversation", 0, 0, 0, (void*) some_conversation_binding);
-    define_primitive("agent:wait", 0, 1, 0, (void*) wait_binding);
-    define_primitive("agent:ping", 1, 0, 0, (void*) ping_binding);
-    define_primitive("agent:set-details", 1, 0, 0, (void*) set_details_binding);
-    define_primitive("agent:get-details", 0, 0, 0, (void*) get_details_binding);
-    define_primitive("agent:ensure-account", 0, 0, 0, (void*) ensure_account_binding);
-    define_primitive("agent->archive", 1, 0, 0, (void*) export_to_archive_binding);
-    define_primitive("archive->agent", 1, 0, 0, (void*) import_from_archive_binding);
-    define_primitive("agent:enable", 0, 0, 0, (void*) enable_binding);
-    define_primitive("agent:disable", 0, 0, 0, (void*) disable_binding);
-    define_primitive("agent:wait-for-call-state", 1, 0, 0, (void*) wait_for_call_state);
-}
diff --git a/test/agent/src/bindings/bindings.cpp b/test/agent/src/bindings/bindings.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b71dfc66db2f948117ec7d3f0d7d833e6003815
--- /dev/null
+++ b/test/agent/src/bindings/bindings.cpp
@@ -0,0 +1,63 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Olivier Dion <olivier.dion@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+/* Agent */
+#include "bindings/bindings.h"
+
+/* Include module's bindings here */
+
+void
+install_scheme_primitives()
+{
+    /* Define modules here */
+}
+
+/*
+ * Register Guile bindings here.
+ *
+ * 1. Name of the binding
+ * 2. Number of required argument to binding
+ * 3. Number of optional argument to binding
+ * 4. Number of rest argument to binding
+ * 5. Pointer to C function to call
+ *
+ * See info guile:
+ *
+ * Function: SCM scm_c_define_gsubr(const char *name, int req, int opt, int rst, fcn):
+ *
+ * Register a C procedure FCN as a “subr” — a primitive subroutine that can be
+ * called from Scheme.  It will be associated with the given NAME and bind it in
+ * the "current environment".  The arguments REQ, OPT and RST specify the number
+ * of required, optional and “rest” arguments respectively.  The total number of
+ * these arguments should match the actual number of arguments to FCN, but may
+ * not exceed 10.  The number of rest arguments should be 0 or 1.
+ * ‘scm_c_make_gsubr’ returns a value of type ‘SCM’ which is a “handle” for the
+ * procedure.
+ */
+void
+define_primitive(const char* name, int req, int opt, int rst, void* func)
+{
+        AGENT_ASSERT(req + opt + rst <= 10, "Primitive binding `%s` has too many argument", name);
+
+        AGENT_ASSERT(0 == rst or 1 == rst, "Rest argument for binding `%s` must be 0 or 1", name);
+
+        scm_c_define_gsubr(name, req, opt, rst, func);
+        scm_c_export(name, NULL);
+}
diff --git a/test/agent/bindings.h b/test/agent/src/bindings/bindings.h
similarity index 87%
rename from test/agent/bindings.h
rename to test/agent/src/bindings/bindings.h
index 85ac943267cda372e6b0f0e56cbd1fe79c7ffb48..fc88c69edd3adec0ecb28513085e7c6558749bdf 100644
--- a/test/agent/bindings.h
+++ b/test/agent/src/bindings/bindings.h
@@ -20,8 +20,11 @@
 
 #pragma once
 
+/* Guile */
 #include <libguile.h>
 
-#define scm_to_cxx_string(VAR) (scm_to_cxx_string)(VAR, #VAR)
+/* Agent */
+#include "utils.h"
 
+extern void define_primitive(const char* name, int req, int opt, int rst, void* func);
 extern void install_scheme_primitives();
diff --git a/test/agent/main.cpp b/test/agent/src/main.cpp
similarity index 70%
rename from test/agent/main.cpp
rename to test/agent/src/main.cpp
index 56c5e2a98b9400fcb1b9da2185ea3134fa1df3ac..8c4da751a178c581a1eb51815778ff7a98e416f1 100644
--- a/test/agent/main.cpp
+++ b/test/agent/src/main.cpp
@@ -18,56 +18,39 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-/* agent */
-#include "agent/agent.h"
-#include "agent/bindings.h"
+/* Agent */
+#include "bindings/bindings.h"
+#include "utils.h"
 
 /* Jami */
 #include "jami.h"
 
 /* Third parties */
-#include <gnutls/gnutls.h>
 #include <libguile.h>
 
-/* std */
-#include <fstream>
-
-static void
-fini()
-{
-    Agent::instance().fini();
-    DRing::fini();
-}
-
 struct args {
     int argc;
     char** argv;
 };
 
 void*
-main_inner(void* args_raw) /* In Guile context */
+main_in_guile(void* args_raw)
 {
-    struct args* args = (struct args*)args_raw;
+    struct args* args = static_cast<struct args*>(args_raw);
 
     install_scheme_primitives();
 
-    Agent::instance().init();
-
-    atexit(fini);
+    atexit(DRing::fini);
 
     scm_shell(args->argc, args->argv);
 
+    /* unreachable */
     return nullptr;
 }
 
 int
 main(int argc, char* argv[])
 {
-    if (argc < 2) {
-        printf("Usage: agent CONFIG\n");
-        exit(EXIT_FAILURE);
-    }
-
     setenv("GUILE_LOAD_PATH", ".", 1);
 
     /* NOTE!  It's very important to initialize the daemon before entering Guile!!! */
@@ -75,11 +58,8 @@ main(int argc, char* argv[])
 
     AGENT_ASSERT(DRing::start(""), "Failed to start daemon");
 
-    struct args args;
-
-    args.argc = argc;
-    args.argv = argv;
+    struct args args = { argc, argv };
 
-    /* Entering guile context */
-    scm_with_guile(main_inner, (void*)&args);
+    /* Entering guile context - This never returns */
+    scm_with_guile(main_in_guile, (void*)&args);
 }
diff --git a/test/agent/src/utils.h b/test/agent/src/utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..be2d82b9f2af0bb4107e339fd7cdf4c538ac5791
--- /dev/null
+++ b/test/agent/src/utils.h
@@ -0,0 +1,168 @@
+/*
+ *  Copyright (C) 2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Olivier Dion <olivier.dion@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#pragma once
+
+#include "logger.h"
+
+#define AGENT_ERR(FMT, ARGS...)  JAMI_ERR("AGENT: " FMT, ##ARGS)
+#define AGENT_INFO(FMT, ARGS...) JAMI_INFO("AGENT: " FMT, ##ARGS)
+#define AGENT_DBG(FMT, ARGS...)  JAMI_DBG("AGENT: " FMT, ##ARGS)
+#define AGENT_ASSERT(COND, MSG, ARGS...) \
+        if (not(COND)) {                 \
+                AGENT_ERR(MSG, ##ARGS);  \
+                exit(1);                 \
+        }
+
+static inline SCM
+to_guile(bool b)
+{
+    return scm_from_bool(b);
+}
+
+static inline SCM
+to_guile(const std::string& str)
+{
+    return scm_from_utf8_string(str.c_str());
+}
+
+/* Forward declarations since we call to_guile() recursively for containers */
+template<typename T> static inline SCM to_guile(const std::vector<T>& values);
+template<typename K, typename V> static inline SCM to_guile(const std::map<K, V>& map);
+
+template<typename T>
+static inline SCM
+to_guile(const std::vector<T>& values)
+{
+    SCM vec = scm_c_make_vector(values.size(), SCM_UNDEFINED);
+
+    for (size_t i = 0; i < values.size(); ++i) {
+        SCM_SIMPLE_VECTOR_SET(vec, i, to_guile(values[i]));
+    }
+
+    return vec;
+}
+
+template<typename K, typename V>
+static inline SCM
+to_guile(const std::map<K, V>& map)
+{
+    SCM assoc = SCM_EOL;
+
+    for (auto const& [key, value] : map) {
+        SCM pair = scm_cons(to_guile(key), to_guile(value));
+        assoc = scm_cons(pair, assoc);
+    }
+
+    return assoc;
+}
+
+template<typename... Args>
+static inline SCM
+pack_to_guile(Args... args)
+{
+    SCM lst = SCM_EOL;
+    std::vector<SCM> values = {to_guile(args)...};
+
+    while (values.size()) {
+        lst = scm_cons(values.back(), lst);
+        values.pop_back();
+    }
+
+    return lst;
+}
+
+template<typename... Args>
+static inline SCM
+apply_to_guile(SCM body_proc, Args... args)
+{
+    AGENT_ASSERT(scm_is_true(scm_procedure_p(body_proc)),
+                 "body_proc must be a procedure");
+
+    SCM arglst = pack_to_guile(args...);
+
+    return scm_apply_0(body_proc, arglst);
+}
+
+struct from_guile
+{
+    SCM value;
+
+    from_guile(SCM val)
+        : value(val)
+    {}
+
+    operator bool()
+    {
+        AGENT_ASSERT(scm_is_bool(value), "Scheme value must be of type bool");
+
+        return scm_to_bool(value);
+    }
+
+    operator std::string()
+    {
+        AGENT_ASSERT(scm_is_string(value), "Scheme value must be of type string");
+
+        char* str_raw = scm_to_locale_string(value);
+        std::string ret(str_raw);
+        free(str_raw);
+
+        return ret;
+    }
+
+    template<typename T>
+    operator std::vector<T>()
+    {
+        AGENT_ASSERT(scm_is_simple_vector(value), "Scheme value must be a simple vector");
+
+        std::vector<T> ret;
+
+        ret.reserve(SCM_SIMPLE_VECTOR_LENGTH(value));
+
+        for (size_t i = 0; i < SCM_SIMPLE_VECTOR_LENGTH(value); ++i) {
+            SCM val = SCM_SIMPLE_VECTOR_REF(value, i);
+
+            ret.emplace_back(from_guile(val));
+        }
+
+        return ret;
+    }
+
+    template<typename K, typename V>
+    operator std::map<K, V>()
+    {
+        AGENT_ASSERT(scm_is_true(scm_list_p(value)), "Scheme value mut be a list");
+
+        std::map<K, V> ret;
+
+        while (not scm_is_null(value)) {
+            SCM pair = scm_car(value);
+
+            K key = from_guile(scm_car(pair));
+            V val = from_guile(scm_cdr(pair));
+
+            ret[key] = val;
+
+            value = scm_cdr(value);
+        }
+
+        return ret;
+    }
+};