diff --git a/.clang-format b/.clang-format
index 5737aca1ec7df306124ffc6d805befe3ff6ade12..5c443be69c45337924324a1f56fad9721dac62f6 100644
--- a/.clang-format
+++ b/.clang-format
@@ -9,7 +9,7 @@ SpaceAfterControlStatementKeyword: true
 PointerBindsToType: true
 IncludeBlocks:   Preserve
 UseTab:          Never
-ColumnLimit:     120
+ColumnLimit:     100
 NamespaceIndentation: Inner
 AlignConsecutiveAssignments: true
 ...
diff --git a/core/algorithms/logger.h b/core/algorithms/logger.h
index db88f3074db1feb008d2ce888c04ea5a5c3ce4f2..2ad9e7759625f65c1a5427f09ada6cd74e7292fd 100644
--- a/core/algorithms/logger.h
+++ b/core/algorithms/logger.h
@@ -1,180 +1,165 @@
 #pragma once
 
+#include <array>
+#include <functional>
+#include <ios>
 #include <iostream>
+#include <mutex>
 #include <ostream>
-#include <ios>
 #include <sstream>
 #include <string>
-#include <functional>
-#include <mutex>
-#include <array>
 
 #include <algorithms/service.h>
-//#include <fmt/core.h>
 
 // Simple thread-safe logger with optional overrides by the calling framework
 namespace algorithms {
 
-enum class LogLevel : unsigned {
-  kJunk = 0,
-  kDebug = 1,
-  kInfo = 2,
-  kWarning = 3,
-  kError = 4
-};
+enum class LogLevel : unsigned { kJunk = 0, kDebug = 1, kInfo = 2, kWarning = 3, kError = 4 };
 constexpr std::string_view logLevelName(LogLevel level) {
   // Compiler will warn if not all of the enum is covered
   switch (level) {
-    case LogLevel::kJunk: return "JUNK";
-    case LogLevel::kDebug: return "DEBUG";
-    case LogLevel::kInfo: return "INFO";
-    case LogLevel::kWarning: return "WARNING";
-    case LogLevel::kError: return "ERROR";
+  case LogLevel::kJunk:
+    return "JUNK";
+  case LogLevel::kDebug:
+    return "DEBUG";
+  case LogLevel::kInfo:
+    return "INFO";
+  case LogLevel::kWarning:
+    return "WARNING";
+  case LogLevel::kError:
+    return "ERROR";
   }
 }
 
-// TODO: integrate proper properties to configure the default level
 // Note: the log action is responsible for dealing with concurrent calls
 //       the default LogAction is a thread-safe example
 class LogSvc : public ServiceMixin<LogSvc> {
-  public:
-    using LogAction = std::function<void(LogLevel, std::string_view, std::string_view)>;
-    void defaultLevel(const LogLevel l) {m_level = l;}
-    LogLevel defaultLevel() const {return m_level;}
-    void action(LogAction a) {
-      m_action = a;
-    }
-    void report(const LogLevel l, std::string_view caller, std::string_view msg) const {
-      m_action(l, caller, msg);
-    }
+public:
+  using LogAction = std::function<void(LogLevel, std::string_view, std::string_view)>;
+  void defaultLevel(const LogLevel l) { m_level.set(l); }
+  LogLevel defaultLevel() const { return m_level; }
+  void action(LogAction a) { m_action = a; }
+  void report(const LogLevel l, std::string_view caller, std::string_view msg) const {
+    m_action(l, caller, msg);
+  }
 
-  private:
-    LogLevel m_level = LogLevel::kInfo;
-    LogAction m_action = [](const LogLevel l, std::string_view caller, std::string_view msg) {
-      static std::mutex m;
-      std::lock_guard<std::mutex> lock(m);
-      //fmt::print("%s [%s] %s\n", logLevelName(l), caller, msg);
-      std::cout << logLevelName(l) << " [" << caller << "] " << msg << std::endl;
-    };
+private:
+  Property<LogLevel> m_level{this, "defaultLevel", LogLevel::kInfo};
+  LogAction m_action = [](const LogLevel l, std::string_view caller, std::string_view msg) {
+    static std::mutex m;
+    std::lock_guard<std::mutex> lock(m);
+    // fmt::print("%s [%s] %s\n", logLevelName(l), caller, msg);
+    std::cout << logLevelName(l) << " [" << caller << "] " << msg << std::endl;
+  };
   ALGORITHMS_DEFINE_SERVICE(LogSvc)
 };
 
 namespace detail {
   // Output buffer that calls our global logger's report() function
   class LoggerBuffer : public std::stringbuf {
-    public:
-      LoggerBuffer(const LogLevel l, std::string_view caller) : m_mylevel{l}, m_caller{caller}, m_logger{algorithms::LogSvc::instance()} {}
-      virtual int sync() {
-        // report should deal with concurrency (the minimal version does)
-        m_logger.report(m_mylevel, m_caller, this->str());
-        this->str("");
-        return 0;
-      }
-    private:
-      // The output buffer knows the log level of its associated logger
-      // (eg. is this the debug logger?)
-      LogLevel m_mylevel;
-      const std::string m_caller;
-      const LogSvc& m_logger;
+  public:
+    LoggerBuffer(const LogLevel l, std::string_view caller)
+        : m_mylevel{l}, m_caller{caller}, m_logger{algorithms::LogSvc::instance()} {}
+    virtual int sync() {
+      // report should deal with concurrency (the minimal version does)
+      m_logger.report(m_mylevel, m_caller, this->str());
+      this->str("");
+      return 0;
+    }
+
+  private:
+    // The output buffer knows the log level of its associated logger
+    // (eg. is this the debug logger?)
+    LogLevel m_mylevel;
+    const std::string m_caller;
+    const LogSvc& m_logger;
   };
   // thread-safe output stream for the logger
   class LoggerStream {
-    public: 
-      LoggerStream(std::string_view caller, const LogLevel level, const LogLevel threshold = LogSvc::instance().defaultLevel())
-        : m_buffer{level, caller}
-        , m_os{&m_buffer}
-        , m_level{level}
-        , m_threshold{threshold} {}
-      LoggerStream() = delete;
-      LoggerStream(const LoggerStream&) = delete;
-      
-      template <class Arg>
-      LoggerStream& operator<<(Arg&& streamable) {
-        if (m_level >= m_threshold) {
-          std::lock_guard<std::mutex> lock{m_mutex};
-          m_os << std::forward<Arg>(streamable);
-          return *this;
-        }
+  public:
+    LoggerStream(std::string_view caller, const LogLevel level,
+                 const LogLevel threshold = LogSvc::instance().defaultLevel())
+        : m_buffer{level, caller}, m_os{&m_buffer}, m_level{level}, m_threshold{threshold} {}
+    LoggerStream()                    = delete;
+    LoggerStream(const LoggerStream&) = delete;
+
+    template <class Arg> LoggerStream& operator<<(Arg&& streamable) {
+      if (m_level >= m_threshold) {
+        std::lock_guard<std::mutex> lock{m_mutex};
+        m_os << std::forward<Arg>(streamable);
         return *this;
       }
-      // To support input manipulators such as std::endl
-      // Note: would be better with Concepts
-      using IOManipType1 = std::ostream&(std::ostream&);  // this capturs std::endl;
-      using IOManipType2 = std::ios_base&(std::ios_base&); 
-      LoggerStream& operator<<(IOManipType1* f) {
-        if (m_level >= m_threshold) {
-          std::lock_guard<std::mutex> lock{m_mutex};
-          f(m_os);
-          return *this;
-        }
+      return *this;
+    }
+    // To support input manipulators such as std::endl
+    // Note: would be better with Concepts
+    using IOManipType1 = std::ostream&(std::ostream&); // this capturs std::endl;
+    using IOManipType2 = std::ios_base&(std::ios_base&);
+    LoggerStream& operator<<(IOManipType1* f) {
+      if (m_level >= m_threshold) {
+        std::lock_guard<std::mutex> lock{m_mutex};
+        f(m_os);
         return *this;
       }
-      LoggerStream& operator<<(IOManipType2* f) {
-        if (m_level >= m_threshold) {
-          std::lock_guard<std::mutex> lock{m_mutex};
-          f(m_os);
-          return *this;
-        }
+      return *this;
+    }
+    LoggerStream& operator<<(IOManipType2* f) {
+      if (m_level >= m_threshold) {
+        std::lock_guard<std::mutex> lock{m_mutex};
+        f(m_os);
         return *this;
       }
-      LogLevel threshold() const {return m_threshold;}
-      void threshold(const LogLevel th) {m_threshold = th;}
-
-    private:
-      std::mutex m_mutex;
-      LoggerBuffer m_buffer;
-      std::ostream m_os;
-      const LogLevel m_level;
-      LogLevel m_threshold;
+      return *this;
+    }
+    LogLevel threshold() const { return m_threshold; }
+    void threshold(const LogLevel th) { m_threshold = th; }
+
+  private:
+    std::mutex m_mutex;
+    LoggerBuffer m_buffer;
+    std::ostream m_os;
+    const LogLevel m_level;
+    LogLevel m_threshold;
   };
-}
+} // namespace detail
 
 // Mixin meant to add utility logger functions to algorithms/services/etc
-// TODO: add property to configure log level instead of using member function
 class LoggerMixin {
-  public:
-    LoggerMixin(std::string_view caller, const LogLevel threshold = LogSvc::instance().defaultLevel()) 
+public:
+  LoggerMixin(std::string_view caller, const LogLevel threshold = LogSvc::instance().defaultLevel())
       : m_caller{caller} {
-      level(threshold);
-    }
-  public:
-      void level(const LogLevel threshold) { 
-        m_level = threshold;
-        m_error.threshold(m_level);
-        m_warning.threshold(m_level);
-        m_info.threshold(m_level);
-        m_debug.threshold(m_level);
-        m_junk.threshold(m_level);
-      }
-      LogLevel level() const {return m_level;}
+    level(threshold);
+  }
 
-  protected:
-    detail::LoggerStream& error() {
-      return m_error;
-    }
-    detail::LoggerStream& warning() {
-      return m_warning;
-    }
-    detail::LoggerStream& info() {
-      return m_info;
-    }
-    detail::LoggerStream& debug() {
-      return m_debug;
-    }
-    detail::LoggerStream& junk() {
-      return m_junk;
-    }
+public:
+  // Not done through Properties, as that is the responsible of the base Algo or Service
+  void level(const LogLevel threshold) {
+    m_level = threshold;
+    m_error.threshold(m_level);
+    m_warning.threshold(m_level);
+    m_info.threshold(m_level);
+    m_debug.threshold(m_level);
+    m_junk.threshold(m_level);
+  }
+  LogLevel level() const { return m_level; }
 
-  private:
-    const std::string m_caller;
-    LogLevel m_level;
-    detail::LoggerStream m_error{m_caller, LogLevel::kError};
-    detail::LoggerStream m_warning{m_caller, LogLevel::kWarning};
-    detail::LoggerStream m_info{m_caller, LogLevel::kInfo};
-    detail::LoggerStream m_debug{m_caller, LogLevel::kDebug};
-    detail::LoggerStream m_junk{m_caller, LogLevel::kJunk};
+protected:
+  detail::LoggerStream& error() const { return m_error; }
+  detail::LoggerStream& warning() const { return m_warning; }
+  detail::LoggerStream& info() const { return m_info; }
+  detail::LoggerStream& debug() const { return m_debug; }
+  detail::LoggerStream& junk() const { return m_junk; }
+
+private:
+  const std::string m_caller;
+  LogLevel m_level;
+  mutable detail::LoggerStream m_error{m_caller, LogLevel::kError};
+  mutable detail::LoggerStream m_warning{m_caller, LogLevel::kWarning};
+  mutable detail::LoggerStream m_info{m_caller, LogLevel::kInfo};
+  mutable detail::LoggerStream m_debug{m_caller, LogLevel::kDebug};
+  mutable detail::LoggerStream m_junk{m_caller, LogLevel::kJunk};
 };
 
-}
+} // namespace algorithms
 
 #define endmsg std::flush
diff --git a/core/algorithms/property.h b/core/algorithms/property.h
index 7439fb093f7dc39b67c8155c465050e85a4ed77b..9da6b99d9e7f993cf670bb47f5384b72fa5956ec 100644
--- a/core/algorithms/property.h
+++ b/core/algorithms/property.h
@@ -3,59 +3,141 @@
 #include <any>
 #include <map>
 #include <string>
+#include <vector>
 
 namespace algorithms {
 
-// A property type that auto-registers itself with the property handler
-template <class T>
-class Property {
+// Configuration/property handling
+class Configurable {
+public:
+  class PropertyBase;
+  using PropertyMap = std::map<std::string_view, PropertyBase&>;
+
+  template <typename T, typename U> void setProperty(std::string_view name, U&& value) {
+    m_props.at(name).set(T(std::forward<U>(value)));
+  }
+  template <typename T> T getProperty(std::string_view name) const {
+    return std::any_cast<T>(m_props.at(name).get());
+  }
+  const PropertyMap& getProperties() const { return m_props; }
+  bool hasProperty(std::string_view name) const {
+    return m_props.count(name) && m_props.at(name).hasValue();
+  }
+
+private:
+  void registerProperty(PropertyBase& prop) {
+    // TODO ensure we have unique property names
+    m_props.emplace(prop.name(), prop);
+  }
+
+  PropertyMap m_props;
+
+public:
+  class PropertyBase {
+  public:
+    PropertyBase(std::string_view name) : m_name{name} {}
+    virtual void set(std::any v) = 0;
+    virtual std::any get() const = 0;
+    bool hasValue() const { return m_has_value; }
+    std::string_view name() const { return m_name; }
+
+  protected:
+    std::string_view m_name;
+    bool m_has_value = false;
+  };
+
+  // A property type that auto-registers itself with the property handler
+  // Essentially a simplified and const-like version of Gaudi::Property
+  template <class T> class Property : public PropertyBase {
   public:
     using ValueType = T;
 
-    // Get const reference to the value
-    const ValueType& value() const { return m_value; }
+    Property(Configurable* owner, std::string_view name) : PropertyBase{name} {
+      // TODO what if we have no owner? warn? error?
+      if (owner) {
+        owner->registerProperty(*this);
+      }
+    }
+    Property(Configurable* owner, std::string_view name, const ValueType& v)
+        : Property(owner, name) {
+      set(v);
+    }
 
-    std::string_view name() const { return m_name; }
+    Property()                      = delete;
+    Property(const Property&)       = delete;
+    void operator=(const Property&) = delete;
 
     // Only settable by explicitly calling the value operator
     // as we want the Property to mostly act as if it is constant
-    template <class U> void value(U&& v) { m_value = std::forward<U>(v); }
+    virtual void set(std::any v) {
+      m_value     = std::any_cast<T>(v);
+      m_has_value = true;
+    }
+    // virtual getter for use from PropertyBase - use ::value() instead for a quick
+    // version that does not go through std::any
+    // Get const reference to the value
+    virtual std::any get() const { return m_value; }
+    const ValueType& value() const { return m_value; }
+
+    // automatically cast to T
+    operator T() const {return m_value;}
 
     // act as if this is a const T
-    template <typename U> bool operator==(const U& rhs) const { return m_value == m_value; }
-    template <typename U> bool operator!=(const U& rhs) const { return m_value != m_value; }
+    template <typename U> bool operator==(const U& rhs) const { return m_value == rhs; }
+    template <typename U> bool operator!=(const U& rhs) const { return m_value != rhs; }
     template <typename U> bool operator>(const U& rhs) const { return m_value > rhs; }
     template <typename U> bool operator>=(const U& rhs) const { return m_value >= rhs; }
     template <typename U> bool operator<(const U& rhs) const { return m_value < rhs; }
     template <typename U> bool operator<=(const U& rhs) const { return m_value <= rhs; }
-    template <typename U> auto operator+(const U& rhs) const { return m_value + rhs; }
-    template <typename U> auto operator-(const U& rhs) const { return m_value - rhs; }
-    template <typename U> auto operator*(const U& rhs) const { return m_value * rhs; }
-    template <typename U> auto operator/(const U& rhs) const { return m_value / rhs; }
+    template <typename U> decltype(auto) operator+(const U& rhs) const { return m_value + rhs; }
+    template <typename U> decltype(auto) operator-(const U& rhs) const { return m_value - rhs; }
+    template <typename U> decltype(auto) operator*(const U& rhs) const { return m_value * rhs; }
+    template <typename U> decltype(auto) operator/(const U& rhs) const { return m_value / rhs; }
 
-  private:
-    std::string_view m_name;
-    T m_value;
-};
+    // stl collection helpers if needed
+    // forced to be templated so we only declare them when used
+    template <class U = const ValueType> decltype(auto) size() const { return value().size(); }
+    template <class U = const ValueType> decltype(auto) length() const { return value().length(); }
+    template <class U = const ValueType> decltype(auto) empty() const { return value().empty(); }
+    template <class U = ValueType> decltype(auto) clear() { value().clear(); }
+    template <class U = const ValueType> decltype(auto) begin() const { return value().begin(); }
+    template <class U = const ValueType> decltype(auto) end() const { return value().end(); }
+    template <class U = ValueType> decltype(auto) begin() { return value().begin(); }
+    template <class U = ValueType> decltype(auto) end() { return value().end(); }
+    template <class Arg> decltype(auto) operator[](const Arg& arg) const { return value()[arg]; }
+    template <class Arg> decltype(auto) operator[](const Arg& arg) { return value()[arg]; }
+    template <class U = const ValueType>
+    decltype(auto) find(const typename U::key_type& key) const {
+      return value().find(key);
+    }
+    template <class U = ValueType> decltype(auto) find(const typename U::key_type& key) {
+      return value().find(key);
+    }
+    template <class Arg> decltype(auto) erase(const Arg& arg) { return value().erase(arg); }
 
-// Configuration/property handling
-class Configurable {
-  public:
+    // In case our property has operator (), delegate the operator()
+    template <class... Args>
+    decltype(std::declval<ValueType>()(std::declval<Args&&>()...))
+    operator()(Args&&... args) const {
+      return m_value()(std::forward<Args>(args)...);
+    }
 
   private:
-    std::map<std::string_view, std::any> m_props;
+    T m_value;
+  };
 };
 
-// Property mixin, provides all the configuration functionality for 
+// Property mixin, provides all the configuration functionality for
 // our algorithms and services
 // Currently an alias to Configurable
 using PropertyMixin = Configurable;
 
-}
+} // namespace algorithms
 
 // operator== overload not needed in C++20 as it will call the member version
 #if __cpp_impl_three_way_comparison < 201711
-template <class T, class U> bool operator==(const U& rhs, algorithms::Property<T>& p) {
+template <class T, class U>
+bool operator==(const U& rhs, algorithms::Configurable::Property<T>& p) {
   return p == rhs;
 }
 #endif
diff --git a/core/algorithms/test.cpp b/core/algorithms/test.cpp
index 245bf9ccc99139bbed815187c03f140398dd2f6d..0ef0216417421f8d5b9df3247b0b7722879aa9f9 100644
--- a/core/algorithms/test.cpp
+++ b/core/algorithms/test.cpp
@@ -2,10 +2,13 @@
 
 using namespace algorithms;
 
-class testAlgo : public LoggerMixin {
+// TODO implement PropertyBase? or use boost::any? need something better for setProperty
+// so we don't need to repeat the type unnecessarily. Hard without type erasure though...
+
+class testLogger : public LoggerMixin {
   public:
-  testAlgo() 
-    : LoggerMixin("testAlgo")
+  testLogger() 
+    : LoggerMixin("testLogger")
   {
     std::cout << "should display an info message" << std::endl;
     info() << "1" << endmsg;
@@ -25,8 +28,48 @@ class testAlgo : public LoggerMixin {
   }
 };
 
+class propTest : public PropertyMixin, LoggerMixin {
+  public:
+    propTest() : LoggerMixin("propTest") {}
+    void print() const {
+      info() << "integer property: " << getProperty<int>("integer") << endmsg;
+      info() << "foo property: " << getProperty<std::string>("foo") << endmsg;
+      if (hasProperty("bar")) {
+        info() << "bar property: " << getProperty<std::string>("bar") << endmsg;
+      }
+      if (hasProperty("double")) {
+        info() << "double (non-def.) property: " << getProperty<double>("double") << endmsg;
+      }
+    }
+  private:
+    Property<int> m_int {this, "integer", 3};
+    Property<std::string> m_foo{this, "foo", "foo_property_value"};
+    // and one without a default
+    Property<std::string> m_bar{this, "bar"};
+    Property<double> m_double{this, "double"};
+
+};
+
 int main() {
-  testAlgo t;
+  testLogger tl;
+
+  std::cout << "test of property, will print set properties. Should only print 2 to start (no bar, as it does not have a default value\n";
+  propTest tp;
+  tp.print();
+  tp.setProperty<int>("integer", 10);
+  tp.print();
+  try {
+    tp.setProperty<int>("foo", 1.);
+    std::cout << "Should not have been reached, as foo does not exist" << std::endl;
+    return 1;
+  } catch (...) {
+    std::cout << "setting a property that didn't exist failed popperly" << std::endl;
+  } 
+  std::cout << "Let's now also set a value for the third and fourth property so it gets printed as well" << std::endl;
+  tp.setProperty<std::string>("bar", "bar_value");
+  tp.setProperty<double>("double", 3.1415f);
+  tp.print();
+
 
   return 0;
 }