diff --git a/src/configuration/action_set_index.rs b/src/action_set_index.rs
similarity index 83%
rename from src/configuration/action_set_index.rs
rename to src/action_set_index.rs
index 068fd96..703fa3c 100644
--- a/src/configuration/action_set_index.rs
+++ b/src/action_set_index.rs
@@ -1,9 +1,9 @@
-use crate::configuration::action_set::ActionSet;
+use crate::runtime_action_set::RuntimeActionSet;
 use radix_trie::Trie;
 use std::rc::Rc;
 
-pub struct ActionSetIndex {
-    raw_tree: Trie<String, Vec<Rc<ActionSet>>>,
+pub(crate) struct ActionSetIndex {
+    raw_tree: Trie<String, Vec<Rc<RuntimeActionSet>>>,
 }
 
 impl ActionSetIndex {
@@ -13,7 +13,7 @@ impl ActionSetIndex {
         }
     }
 
-    pub fn insert(&mut self, subdomain: &str, action_set: Rc<ActionSet>) {
+    pub fn insert(&mut self, subdomain: &str, action_set: Rc<RuntimeActionSet>) {
         let rev = Self::reverse_subdomain(subdomain);
         self.raw_tree.map_with_default(
             rev,
@@ -24,7 +24,10 @@ impl ActionSetIndex {
         );
     }
 
-    pub fn get_longest_match_action_sets(&self, subdomain: &str) -> Option<&Vec<Rc<ActionSet>>> {
+    pub fn get_longest_match_action_sets(
+        &self,
+        subdomain: &str,
+    ) -> Option<&Vec<Rc<RuntimeActionSet>>> {
         let rev = Self::reverse_subdomain(subdomain);
         self.raw_tree.get_ancestor_value(&rev)
     }
@@ -43,12 +46,16 @@ impl ActionSetIndex {
 
 #[cfg(test)]
 mod tests {
-    use crate::configuration::action_set::ActionSet;
-    use crate::configuration::action_set_index::ActionSetIndex;
+    use crate::action_set_index::ActionSetIndex;
+    use crate::runtime_action_set::RuntimeActionSet;
     use std::rc::Rc;
 
-    fn build_ratelimit_action_set(name: &str) -> ActionSet {
-        ActionSet::new(name.to_owned(), Default::default(), Vec::new())
+    fn build_ratelimit_action_set(name: &str) -> RuntimeActionSet {
+        RuntimeActionSet {
+            name: name.to_owned(),
+            route_rule_predicates: Default::default(),
+            runtime_actions: Vec::new(),
+        }
     }
 
     #[test]
diff --git a/src/auth_action.rs b/src/auth_action.rs
new file mode 100644
index 0000000..94af576
--- /dev/null
+++ b/src/auth_action.rs
@@ -0,0 +1,111 @@
+use crate::configuration::{Action, FailureMode, Service};
+use crate::data::Predicate;
+use crate::service::GrpcService;
+use log::error;
+use std::rc::Rc;
+
+#[derive(Debug)]
+pub struct AuthAction {
+    grpc_service: Rc<GrpcService>,
+    scope: String,
+    predicates: Vec<Predicate>,
+}
+
+impl AuthAction {
+    pub fn new(action: &Action, service: &Service) -> Result<Self, String> {
+        let mut predicates = Vec::default();
+        for predicate in &action.predicates {
+            predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?);
+        }
+
+        Ok(AuthAction {
+            grpc_service: Rc::new(GrpcService::new(Rc::new(service.clone()))),
+            scope: action.scope.clone(),
+            predicates,
+        })
+    }
+
+    pub fn get_grpcservice(&self) -> Rc<GrpcService> {
+        Rc::clone(&self.grpc_service)
+    }
+
+    pub fn scope(&self) -> &str {
+        self.scope.as_str()
+    }
+
+    pub fn conditions_apply(&self) -> bool {
+        let predicates = &self.predicates;
+        predicates.is_empty()
+            || predicates.iter().all(|predicate| match predicate.test() {
+                Ok(b) => b,
+                Err(err) => {
+                    error!("Failed to evaluate {:?}: {}", predicate, err);
+                    panic!("Err out of this!")
+                }
+            })
+    }
+
+    pub fn get_failure_mode(&self) -> FailureMode {
+        self.grpc_service.get_failure_mode()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::configuration::{Action, FailureMode, Service, ServiceType, Timeout};
+
+    fn build_auth_action_with_predicates(predicates: Vec<String>) -> AuthAction {
+        let action = Action {
+            service: "some_service".into(),
+            scope: "some_scope".into(),
+            predicates,
+            data: Vec::default(),
+        };
+
+        let service = Service {
+            service_type: ServiceType::Auth,
+            endpoint: "some_endpoint".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        };
+
+        AuthAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?")
+    }
+
+    #[test]
+    fn empty_predicates_do_apply() {
+        let auth_action = build_auth_action_with_predicates(Vec::default());
+        assert!(auth_action.conditions_apply());
+    }
+
+    #[test]
+    fn when_all_predicates_are_truthy_action_apply() {
+        let auth_action = build_auth_action_with_predicates(vec!["true".into(), "true".into()]);
+        assert!(auth_action.conditions_apply());
+    }
+
+    #[test]
+    fn when_not_all_predicates_are_truthy_action_does_not_apply() {
+        let auth_action = build_auth_action_with_predicates(vec![
+            "true".into(),
+            "true".into(),
+            "true".into(),
+            "false".into(),
+        ]);
+        assert!(!auth_action.conditions_apply());
+    }
+
+    #[test]
+    #[should_panic]
+    fn when_a_cel_expression_does_not_evaluate_to_bool_panics() {
+        let auth_action = build_auth_action_with_predicates(vec![
+            "true".into(),
+            "true".into(),
+            "true".into(),
+            "1".into(),
+        ]);
+        auth_action.conditions_apply();
+    }
+}
diff --git a/src/configuration.rs b/src/configuration.rs
index 7f9771f..5dfdea7 100644
--- a/src/configuration.rs
+++ b/src/configuration.rs
@@ -1,39 +1,58 @@
-use std::cell::OnceCell;
 use std::collections::HashMap;
 use std::fmt::{Debug, Formatter};
-use std::rc::Rc;
 use std::sync::Arc;
 
-use crate::configuration::action_set::ActionSet;
-use crate::configuration::action_set_index::ActionSetIndex;
-use crate::data;
-use crate::data::Predicate;
-use crate::service::GrpcService;
 use cel_interpreter::functions::time::duration;
 use cel_interpreter::Value;
 use serde::de::{Error, Visitor};
 use serde::{Deserialize, Deserializer};
 use std::time::Duration;
 
-pub mod action;
-pub mod action_set;
-mod action_set_index;
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct Action {
+    pub service: String,
+    pub scope: String,
+    #[serde(default)]
+    pub predicates: Vec<String>,
+    #[serde(default)]
+    pub data: Vec<DataItem>,
+}
+
+#[derive(Deserialize, Debug, Clone, Default)]
+pub struct RouteRuleConditions {
+    pub hostnames: Vec<String>,
+    #[serde(default)]
+    pub predicates: Vec<String>,
+}
+
+#[derive(Default, Deserialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ActionSet {
+    pub name: String,
+    pub route_rule_conditions: RouteRuleConditions,
+    pub actions: Vec<Action>,
+}
+
+impl ActionSet {
+    #[cfg(test)]
+    pub fn new(
+        name: String,
+        route_rule_conditions: RouteRuleConditions,
+        actions: Vec<Action>,
+    ) -> Self {
+        ActionSet {
+            name,
+            route_rule_conditions,
+            actions,
+        }
+    }
+}
 
 #[derive(Deserialize, Debug, Clone)]
 pub struct ExpressionItem {
     pub key: String,
     pub value: String,
-    #[serde(skip_deserializing)]
-    pub compiled: OnceCell<data::Expression>,
-}
-
-impl ExpressionItem {
-    pub fn compile(&self) -> Result<(), String> {
-        self.compiled
-            .set(data::Expression::new(&self.value).map_err(|e| e.to_string())?)
-            .expect("Expression must not be compiled yet!");
-        Ok(())
-    }
 }
 
 #[derive(Deserialize, Debug, Clone)]
@@ -50,15 +69,6 @@ pub enum DataType {
     Expression(ExpressionItem),
 }
 
-impl DataType {
-    pub fn compile(&self) -> Result<(), String> {
-        match self {
-            DataType::Static(_) => Ok(()),
-            DataType::Expression(exp) => exp.compile(),
-        }
-    }
-}
-
 #[derive(Deserialize, Debug, Clone)]
 #[serde(deny_unknown_fields)]
 pub struct DataItem {
@@ -66,69 +76,6 @@ pub struct DataItem {
     pub item: DataType,
 }
 
-pub struct FilterConfig {
-    pub index: ActionSetIndex,
-    pub services: Rc<HashMap<String, Rc<GrpcService>>>,
-}
-
-impl Default for FilterConfig {
-    fn default() -> Self {
-        Self {
-            index: ActionSetIndex::new(),
-            services: Rc::new(HashMap::new()),
-        }
-    }
-}
-
-impl TryFrom<PluginConfiguration> for FilterConfig {
-    type Error = String;
-
-    fn try_from(config: PluginConfiguration) -> Result<Self, Self::Error> {
-        let mut index = ActionSetIndex::new();
-        for action_set in config.action_sets.iter() {
-            let mut predicates = Vec::default();
-            for predicate in &action_set.route_rule_conditions.predicates {
-                predicates.push(Predicate::route_rule(predicate).map_err(|e| e.to_string())?);
-            }
-            action_set
-                .route_rule_conditions
-                .compiled_predicates
-                .set(predicates)
-                .expect("Predicates must not be compiled yet!");
-            for action in &action_set.actions {
-                let mut predicates = Vec::default();
-                for predicate in &action.predicates {
-                    predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?);
-                }
-                action
-                    .compiled_predicates
-                    .set(predicates)
-                    .expect("Predicates must not be compiled yet!");
-
-                for datum in &action.data {
-                    datum.item.compile()?;
-                }
-            }
-
-            for hostname in action_set.route_rule_conditions.hostnames.iter() {
-                index.insert(hostname, Rc::new(action_set.clone()));
-            }
-        }
-
-        // configure grpc services from the services in config
-        let services = config
-            .services
-            .into_iter()
-            .map(|(name, ext)| (name, Rc::new(GrpcService::new(Rc::new(ext)))))
-            .collect();
-
-        Ok(Self {
-            index,
-            services: Rc::new(services),
-        })
-    }
-}
-
 #[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq)]
 #[serde(rename_all = "lowercase")]
 pub enum FailureMode {
@@ -277,10 +224,10 @@ mod test {
         }
         assert!(res.is_ok());
 
-        let filter_config = res.expect("result is ok");
-        assert_eq!(filter_config.action_sets.len(), 1);
+        let plugin_config = res.expect("result is ok");
+        assert_eq!(plugin_config.action_sets.len(), 1);
 
-        let services = &filter_config.services;
+        let services = &plugin_config.services;
         assert_eq!(services.len(), 2);
 
         if let Some(auth_service) = services.get("authorino") {
@@ -301,12 +248,12 @@ mod test {
             panic!()
         }
 
-        let predicates = &filter_config.action_sets[0]
+        let predicates = &plugin_config.action_sets[0]
             .route_rule_conditions
             .predicates;
         assert_eq!(predicates.len(), 3);
 
-        let actions = &filter_config.action_sets[0].actions;
+        let actions = &plugin_config.action_sets[0].actions;
         assert_eq!(actions.len(), 2);
 
         let auth_action = &actions[0];
@@ -326,17 +273,6 @@ mod test {
         let rl_predicates = &rl_action.predicates;
         assert_eq!(rl_predicates.len(), 1);
 
-        // TODO(eastizle): DataItem does not implement PartialEq, add it only for testing?
-        //assert_eq!(
-        //    data_items[0],
-        //    DataItem {
-        //        item: DataType::Static(StaticItem {
-        //            key: String::from("rlp-ns-A/rlp-name-A"),
-        //            value: String::from("1")
-        //        })
-        //    }
-        //);
-
         if let DataType::Static(static_item) = &rl_data_items[0].item {
             assert_eq!(static_item.key, "rlp-ns-A/rlp-name-A");
             assert_eq!(static_item.value, "1");
@@ -364,8 +300,8 @@ mod test {
         }
         assert!(res.is_ok());
 
-        let filter_config = res.expect("result is ok");
-        assert_eq!(filter_config.action_sets.len(), 0);
+        let plugin_config = res.expect("result is ok");
+        assert_eq!(plugin_config.action_sets.len(), 0);
     }
 
     #[test]
@@ -410,10 +346,10 @@ mod test {
         }
         assert!(res.is_ok());
 
-        let filter_config = res.expect("result is ok");
-        assert_eq!(filter_config.action_sets.len(), 1);
+        let plugin_config = res.expect("result is ok");
+        assert_eq!(plugin_config.action_sets.len(), 1);
 
-        let services = &filter_config.services;
+        let services = &plugin_config.services;
         assert_eq!(
             services
                 .get("limitador")
@@ -422,12 +358,12 @@ mod test {
             Timeout(Duration::from_millis(20))
         );
 
-        let predicates = &filter_config.action_sets[0]
+        let predicates = &plugin_config.action_sets[0]
             .route_rule_conditions
             .predicates;
         assert_eq!(predicates.len(), 0);
 
-        let actions = &filter_config.action_sets[0].actions;
+        let actions = &plugin_config.action_sets[0].actions;
         assert_eq!(actions.len(), 1);
 
         let action_predicates = &actions[0].predicates;
@@ -504,28 +440,4 @@ mod test {
         let res = serde_json::from_str::<PluginConfiguration>(bad_config);
         assert!(res.is_err());
     }
-
-    #[test]
-    fn filter_config_from_configuration() {
-        let res = serde_json::from_str::<PluginConfiguration>(CONFIG);
-        if let Err(ref e) = res {
-            eprintln!("{e}");
-        }
-        assert!(res.is_ok());
-
-        let result = FilterConfig::try_from(res.expect("result is ok"));
-        let filter_config = result.expect("That didn't work");
-        let rlp_option = filter_config
-            .index
-            .get_longest_match_action_sets("example.com");
-        assert!(rlp_option.is_some());
-
-        let rlp_option = filter_config
-            .index
-            .get_longest_match_action_sets("test.toystore.com");
-        assert!(rlp_option.is_some());
-
-        let rlp_option = filter_config.index.get_longest_match_action_sets("unknown");
-        assert!(rlp_option.is_none());
-    }
 }
diff --git a/src/configuration/action.rs b/src/configuration/action.rs
deleted file mode 100644
index 58e893f..0000000
--- a/src/configuration/action.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use crate::configuration::{DataItem, DataType};
-use crate::data::Predicate;
-use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry};
-use cel_interpreter::Value;
-use log::error;
-use protobuf::RepeatedField;
-use serde::Deserialize;
-use std::cell::OnceCell;
-
-#[derive(Deserialize, Debug, Clone)]
-#[serde(rename_all = "camelCase")]
-pub struct Action {
-    pub service: String,
-    pub scope: String,
-    #[serde(default)]
-    pub predicates: Vec<String>,
-    #[serde(skip_deserializing)]
-    pub compiled_predicates: OnceCell<Vec<Predicate>>,
-    #[serde(default)]
-    pub data: Vec<DataItem>,
-}
-
-impl Action {
-    pub fn conditions_apply(&self) -> bool {
-        let predicates = self
-            .compiled_predicates
-            .get()
-            .expect("predicates must be compiled by now");
-        predicates.is_empty()
-            || predicates
-                .iter()
-                .enumerate()
-                .all(|(pos, predicate)| match predicate.test() {
-                    Ok(b) => b,
-                    Err(err) => {
-                        error!("Failed to evaluate {}: {}", self.predicates[pos], err);
-                        panic!("Err out of this!")
-                    }
-                })
-    }
-
-    pub fn build_descriptors(&self) -> RepeatedField<RateLimitDescriptor> {
-        let mut entries = RepeatedField::new();
-        if let Some(desc) = self.build_single_descriptor() {
-            entries.push(desc);
-        }
-        entries
-    }
-
-    fn build_single_descriptor(&self) -> Option<RateLimitDescriptor> {
-        let mut entries = RepeatedField::default();
-
-        // iterate over data items to allow any data item to skip the entire descriptor
-        for data in self.data.iter() {
-            let (key, value) = match &data.item {
-                DataType::Static(static_item) => {
-                    (static_item.key.to_owned(), static_item.value.to_owned())
-                }
-                DataType::Expression(cel) => (
-                    cel.key.clone(),
-                    match cel
-                        .compiled
-                        .get()
-                        .expect("Expression must be compiled by now")
-                        .eval()
-                    {
-                        Ok(value) => match value {
-                            Value::Int(n) => format!("{n}"),
-                            Value::UInt(n) => format!("{n}"),
-                            Value::Float(n) => format!("{n}"),
-                            // todo this probably should be a proper string literal!
-                            Value::String(s) => (*s).clone(),
-                            Value::Bool(b) => format!("{b}"),
-                            Value::Null => "null".to_owned(),
-                            _ => panic!("Only scalar values can be sent as data"),
-                        },
-                        Err(err) => {
-                            error!("Failed to evaluate {}: {}", cel.value, err);
-                            panic!("Err out of this!")
-                        }
-                    },
-                ),
-            };
-            let mut descriptor_entry = RateLimitDescriptor_Entry::new();
-            descriptor_entry.set_key(key);
-            descriptor_entry.set_value(value);
-            entries.push(descriptor_entry);
-        }
-        let mut res = RateLimitDescriptor::new();
-        res.set_entries(entries);
-        Some(res)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::configuration::action::Action;
-    use std::cell::OnceCell;
-
-    #[test]
-    fn empty_predicates_do_apply() {
-        let compiled_predicates = OnceCell::new();
-        compiled_predicates
-            .set(Vec::default())
-            .expect("predicates must not be compiled yet!");
-
-        let action = Action {
-            service: String::from("svc1"),
-            scope: String::from("sc1"),
-            predicates: vec![],
-            compiled_predicates,
-            data: vec![],
-        };
-
-        assert!(action.conditions_apply())
-    }
-}
diff --git a/src/configuration/action_set.rs b/src/configuration/action_set.rs
deleted file mode 100644
index 1e8a61a..0000000
--- a/src/configuration/action_set.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-use crate::configuration::action::Action;
-use crate::data::Predicate;
-use log::error;
-use serde::Deserialize;
-use std::cell::OnceCell;
-
-#[derive(Deserialize, Debug, Clone, Default)]
-pub struct RouteRuleConditions {
-    pub hostnames: Vec<String>,
-    #[serde(default)]
-    pub predicates: Vec<String>,
-    #[serde(skip_deserializing)]
-    pub compiled_predicates: OnceCell<Vec<Predicate>>,
-}
-
-#[derive(Default, Deserialize, Debug, Clone)]
-#[serde(rename_all = "camelCase")]
-pub struct ActionSet {
-    pub name: String,
-    pub route_rule_conditions: RouteRuleConditions,
-    pub actions: Vec<Action>,
-}
-
-impl ActionSet {
-    #[cfg(test)]
-    pub fn new(
-        name: String,
-        route_rule_conditions: RouteRuleConditions,
-        actions: Vec<Action>,
-    ) -> Self {
-        ActionSet {
-            name,
-            route_rule_conditions,
-            actions,
-        }
-    }
-
-    pub fn conditions_apply(&self) -> bool {
-        let predicates = self
-            .route_rule_conditions
-            .compiled_predicates
-            .get()
-            .expect("predicates must be compiled by now");
-        predicates.is_empty()
-            || predicates
-                .iter()
-                .enumerate()
-                .all(|(pos, predicate)| match predicate.test() {
-                    Ok(b) => b,
-                    Err(err) => {
-                        error!(
-                            "Failed to evaluate {}: {}",
-                            self.route_rule_conditions.predicates[pos], err
-                        );
-                        panic!("Err out of this!")
-                    }
-                })
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::configuration::action_set::ActionSet;
-
-    fn build_action_set(name: &str) -> ActionSet {
-        ActionSet::new(name.to_owned(), Default::default(), Vec::new())
-    }
-
-    #[test]
-    fn empty_route_rule_conditions_do_apply() {
-        let action_set_1 = build_action_set("as_1");
-        action_set_1
-            .route_rule_conditions
-            .compiled_predicates
-            .set(Vec::default())
-            .expect("Predicates must not be compiled yet!");
-
-        assert!(action_set_1.conditions_apply())
-    }
-}
diff --git a/src/filter/http_context.rs b/src/filter/http_context.rs
index e9204d1..e7fd0d7 100644
--- a/src/filter/http_context.rs
+++ b/src/filter/http_context.rs
@@ -1,8 +1,9 @@
-use crate::configuration::action_set::ActionSet;
-use crate::configuration::{FailureMode, FilterConfig};
+use crate::configuration::FailureMode;
 #[cfg(feature = "debug-host-behaviour")]
 use crate::data;
 use crate::operation_dispatcher::{OperationDispatcher, OperationError};
+use crate::runtime_action_set::RuntimeActionSet;
+use crate::runtime_config::RuntimeConfig;
 use crate::service::GrpcService;
 use log::{debug, warn};
 use proxy_wasm::traits::{Context, HttpContext};
@@ -12,7 +13,7 @@ use std::rc::Rc;
 
 pub struct Filter {
     pub context_id: u32,
-    pub config: Rc<FilterConfig>,
+    pub config: Rc<RuntimeConfig>,
     pub response_headers_to_add: Vec<(String, String)>,
     pub operation_dispatcher: RefCell<OperationDispatcher>,
 }
@@ -32,22 +33,13 @@ impl Filter {
     }
 
     #[allow(unknown_lints, clippy::manual_inspect)]
-    fn process_action_sets(&self, action_sets: &[Rc<ActionSet>]) -> Action {
-        if let Some(action_set) = action_sets
-            .iter()
-            .find(|action_set| action_set.conditions_apply())
-        {
-            debug!(
-                "#{} action_set selected {}",
-                self.context_id, action_set.name
-            );
-            if let Err(op_err) = self
-                .operation_dispatcher
+    fn process_action_sets(&self, m_set_list: &[Rc<RuntimeActionSet>]) -> Action {
+        if let Some(m_set) = m_set_list.iter().find(|m_set| m_set.conditions_apply()) {
+            debug!("#{} action_set selected {}", self.context_id, m_set.name);
+            //debug!("#{} runtime action_set {:#?}", self.context_id, m_set);
+            self.operation_dispatcher
                 .borrow_mut()
-                .build_operations(&action_set.actions)
-            {
-                self.send_http_response(500, vec![], Some(format!("{op_err}").as_ref()));
-            }
+                .build_operations(&m_set.runtime_actions)
         } else {
             debug!(
                 "#{} process_action_sets: no action_set with conditions applies",
@@ -111,7 +103,7 @@ impl HttpContext for Filter {
                 );
                 Action::Continue
             }
-            Some(action_sets) => self.process_action_sets(action_sets),
+            Some(m_sets) => self.process_action_sets(m_sets),
         }
     }
 
diff --git a/src/filter/root_context.rs b/src/filter/root_context.rs
index 01b29ff..4716387 100644
--- a/src/filter/root_context.rs
+++ b/src/filter/root_context.rs
@@ -1,12 +1,12 @@
-use crate::configuration::{FilterConfig, PluginConfiguration};
+use crate::configuration::PluginConfiguration;
 use crate::filter::http_context::Filter;
 use crate::operation_dispatcher::OperationDispatcher;
-use crate::service::{GrpcServiceHandler, HeaderResolver};
+use crate::runtime_config::RuntimeConfig;
+use crate::service::HeaderResolver;
 use const_format::formatcp;
 use log::{debug, error, info};
 use proxy_wasm::traits::{Context, HttpContext, RootContext};
 use proxy_wasm::types::ContextType;
-use std::collections::HashMap;
 use std::rc::Rc;
 
 const WASM_SHIM_VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -17,7 +17,7 @@ const WASM_SHIM_HEADER: &str = "Kuadrant wasm module";
 
 pub struct FilterRoot {
     pub context_id: u32,
-    pub config: Rc<FilterConfig>,
+    pub config: Rc<RuntimeConfig>,
 }
 
 impl RootContext for FilterRoot {
@@ -35,24 +35,12 @@ impl RootContext for FilterRoot {
 
     fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
         debug!("#{} create_http_context", context_id);
-        let mut service_handlers: HashMap<String, Rc<GrpcServiceHandler>> = HashMap::new();
         let header_resolver = Rc::new(HeaderResolver::new());
-        self.config
-            .services
-            .iter()
-            .for_each(|(service_name, grpc_service)| {
-                service_handlers
-                    .entry(service_name.clone())
-                    .or_insert(Rc::from(GrpcServiceHandler::new(
-                        Rc::clone(grpc_service),
-                        Rc::clone(&header_resolver),
-                    )));
-            });
         Some(Box::new(Filter {
             context_id,
             config: Rc::clone(&self.config),
             response_headers_to_add: Vec::default(),
-            operation_dispatcher: OperationDispatcher::new(service_handlers).into(),
+            operation_dispatcher: OperationDispatcher::new(header_resolver).into(),
         }))
     }
 
@@ -65,15 +53,15 @@ impl RootContext for FilterRoot {
         match serde_json::from_slice::<PluginConfiguration>(&configuration) {
             Ok(config) => {
                 info!("plugin config parsed: {:?}", config);
-                let filter_config =
-                    match <PluginConfiguration as TryInto<FilterConfig>>::try_into(config) {
+                let runtime_config =
+                    match <PluginConfiguration as TryInto<RuntimeConfig>>::try_into(config) {
                         Ok(cfg) => cfg,
                         Err(err) => {
                             error!("failed to compile plugin config: {}", err);
                             return false;
                         }
                     };
-                self.config = Rc::new(filter_config);
+                self.config = Rc::new(runtime_config);
             }
             Err(e) => {
                 error!("failed to parse plugin config: {}", e);
diff --git a/src/lib.rs b/src/lib.rs
index ffaf17a..2062ebd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
+mod action_set_index;
+mod auth_action;
 mod configuration;
 mod data;
 #[allow(renamed_and_removed_lints)]
@@ -5,6 +7,10 @@ mod envoy;
 mod filter;
 mod glob;
 mod operation_dispatcher;
+mod ratelimit_action;
+mod runtime_action;
+mod runtime_action_set;
+mod runtime_config;
 mod service;
 
 #[cfg(test)]
diff --git a/src/operation_dispatcher.rs b/src/operation_dispatcher.rs
index 3b7779c..156b08f 100644
--- a/src/operation_dispatcher.rs
+++ b/src/operation_dispatcher.rs
@@ -1,7 +1,9 @@
-use crate::configuration::action::Action;
-use crate::configuration::{FailureMode, Service, ServiceType};
+use crate::configuration::{FailureMode, ServiceType};
+use crate::runtime_action::RuntimeAction;
 use crate::service::grpc_message::GrpcMessageRequest;
-use crate::service::{GetMapValuesBytesFn, GrpcCallFn, GrpcMessageBuildFn, GrpcServiceHandler};
+use crate::service::{
+    GetMapValuesBytesFn, GrpcCallFn, GrpcMessageBuildFn, GrpcServiceHandler, HeaderResolver,
+};
 use log::{debug, error};
 use proxy_wasm::hostcalls;
 use proxy_wasm::types::{Bytes, MapType, Status};
@@ -32,13 +34,12 @@ impl State {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub(crate) struct Operation {
     state: RefCell<State>,
     result: RefCell<Result<u32, OperationError>>,
-    service: Rc<Service>,
-    action: Action,
-    service_handler: Rc<GrpcServiceHandler>,
+    action: Rc<RuntimeAction>,
+    service_handler: GrpcServiceHandler,
     grpc_call_fn: GrpcCallFn,
     get_map_values_bytes_fn: GetMapValuesBytesFn,
     grpc_message_build_fn: GrpcMessageBuildFn,
@@ -46,15 +47,10 @@ pub(crate) struct Operation {
 }
 
 impl Operation {
-    pub fn new(
-        service: Rc<Service>,
-        action: Action,
-        service_handler: Rc<GrpcServiceHandler>,
-    ) -> Self {
+    pub fn new(action: Rc<RuntimeAction>, service_handler: GrpcServiceHandler) -> Self {
         Self {
             state: RefCell::new(State::Pending),
             result: RefCell::new(Ok(0)), // Heuristics: zero represents that it's not been triggered, following `hostcalls` example
-            service,
             action,
             service_handler,
             grpc_call_fn,
@@ -65,12 +61,12 @@ impl Operation {
     }
 
     fn trigger(&self) -> Result<u32, OperationError> {
-        if let Some(message) = (self.grpc_message_build_fn)(self.get_service_type(), &self.action) {
+        if let Some(message) = (self.grpc_message_build_fn)(&self.action) {
             let res = self.service_handler.send(
                 self.get_map_values_bytes_fn,
                 self.grpc_call_fn,
                 message,
-                self.service.timeout.0,
+                self.action.get_timeout(),
             );
             match res {
                 Ok(token_id) => self.set_result(Ok(token_id)),
@@ -106,14 +102,15 @@ impl Operation {
         *self.result.borrow_mut() = result;
     }
 
-    pub fn get_service_type(&self) -> &ServiceType {
-        &self.service.service_type
+    pub fn get_service_type(&self) -> ServiceType {
+        self.action.get_service_type()
     }
 
     pub fn get_failure_mode(&self) -> FailureMode {
-        self.service.failure_mode
+        self.action.get_failure_mode()
     }
 }
+
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub struct OperationError {
     pub status: Status,
@@ -145,15 +142,15 @@ impl fmt::Display for OperationError {
 pub struct OperationDispatcher {
     operations: Vec<Rc<Operation>>,
     waiting_operations: HashMap<u32, Rc<Operation>>,
-    service_handlers: HashMap<String, Rc<GrpcServiceHandler>>,
+    header_resolver: Rc<HeaderResolver>,
 }
 
 impl OperationDispatcher {
-    pub fn new(service_handlers: HashMap<String, Rc<GrpcServiceHandler>>) -> Self {
+    pub fn new(header_resolver: Rc<HeaderResolver>) -> Self {
         Self {
-            service_handlers,
             operations: vec![],
             waiting_operations: HashMap::new(),
+            header_resolver: Rc::clone(&header_resolver),
         }
     }
 
@@ -171,25 +168,15 @@ impl OperationDispatcher {
         }
     }
 
-    pub fn build_operations(&mut self, actions: &[Action]) -> Result<(), OperationError> {
+    pub fn build_operations(&mut self, actions: &[Rc<RuntimeAction>]) {
         let mut operations: Vec<Rc<Operation>> = vec![];
         for action in actions.iter() {
-            if let Some(service) = self.service_handlers.get(&action.service) {
-                operations.push(Rc::new(Operation::new(
-                    service.get_service(),
-                    action.clone(),
-                    Rc::clone(service),
-                )))
-            } else {
-                error!("Unknown service: {}", action.service);
-                return Err(OperationError::new(
-                    Status::ParseFailure,
-                    Default::default(),
-                ));
-            }
+            operations.push(Rc::new(Operation::new(
+                Rc::clone(action),
+                GrpcServiceHandler::new(action.grpc_service(), Rc::clone(&self.header_resolver)),
+            )));
         }
         self.push_operations(operations);
-        Ok(())
     }
 
     pub fn push_operations(&mut self, operations: Vec<Rc<Operation>>) {
@@ -249,9 +236,10 @@ impl OperationDispatcher {
         OperationDispatcher {
             operations: vec![],
             waiting_operations: HashMap::default(),
-            service_handlers: HashMap::default(),
+            header_resolver: Rc::new(HeaderResolver::default()),
         }
     }
+
     #[cfg(test)]
     pub fn get_current_operation_state(&self) -> Option<State> {
         self.operations
@@ -282,26 +270,25 @@ fn get_map_values_bytes_fn(map_type: MapType, key: &str) -> Result<Option<Bytes>
     hostcalls::get_map_value_bytes(map_type, key)
 }
 
-fn grpc_message_build_fn(
-    extension_type: &ServiceType,
-    action: &Action,
-) -> Option<GrpcMessageRequest> {
-    GrpcMessageRequest::new(extension_type, action)
+fn grpc_message_build_fn(action: &RuntimeAction) -> Option<GrpcMessageRequest> {
+    GrpcMessageRequest::new(action)
 }
 
-type ConditionsApplyFn = fn(action: &Action) -> bool;
+type ConditionsApplyFn = fn(action: &RuntimeAction) -> bool;
 
-fn conditions_apply_fn(action: &Action) -> bool {
+fn conditions_apply_fn(action: &RuntimeAction) -> bool {
     action.conditions_apply()
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::configuration::Timeout;
+    use crate::auth_action::AuthAction;
+    use crate::configuration::{Action, Service, Timeout};
     use crate::envoy::RateLimitRequest;
+    use crate::ratelimit_action::RateLimitAction;
     use protobuf::RepeatedField;
-    use std::cell::OnceCell;
+    use std::rc::Rc;
     use std::time::Duration;
 
     fn default_grpc_call_fn_stub(
@@ -322,18 +309,15 @@ mod tests {
         Ok(Some(Vec::new()))
     }
 
-    fn grpc_message_build_fn_stub(
-        _extension_type: &ServiceType,
-        _action: &Action,
-    ) -> Option<GrpcMessageRequest> {
+    fn grpc_message_build_fn_stub(_action: &RuntimeAction) -> Option<GrpcMessageRequest> {
         Some(GrpcMessageRequest::RateLimit(build_message()))
     }
 
     fn build_grpc_service_handler() -> GrpcServiceHandler {
-        GrpcServiceHandler::new(Rc::new(Default::default()), Rc::new(Default::default()))
+        GrpcServiceHandler::new(Rc::new(Default::default()), Default::default())
     }
 
-    fn conditions_apply_fn_stub(_action: &Action) -> bool {
+    fn conditions_apply_fn_stub(_action: &RuntimeAction) -> bool {
         true
     }
 
@@ -347,27 +331,48 @@ mod tests {
         }
     }
 
-    fn build_operation(
-        grpc_call_fn_stub: GrpcCallFn,
-        extension_type: ServiceType,
-    ) -> Rc<Operation> {
+    fn build_auth_grpc_action() -> RuntimeAction {
+        let service = Service {
+            service_type: ServiceType::Auth,
+            endpoint: "local".to_string(),
+            failure_mode: FailureMode::Deny,
+            timeout: Timeout(Duration::from_millis(42)),
+        };
+        let action = Action {
+            service: "local".to_string(),
+            scope: "".to_string(),
+            predicates: vec![],
+            data: vec![],
+        };
+        RuntimeAction::Auth(
+            AuthAction::new(&action, &service).expect("empty predicates should compile!"),
+        )
+    }
+
+    fn build_rate_limit_grpc_action() -> RuntimeAction {
+        let service = Service {
+            service_type: ServiceType::RateLimit,
+            endpoint: "local".to_string(),
+            failure_mode: FailureMode::Deny,
+            timeout: Timeout(Duration::from_millis(42)),
+        };
+        let action = Action {
+            service: "local".to_string(),
+            scope: "".to_string(),
+            predicates: vec![],
+            data: vec![],
+        };
+        RuntimeAction::RateLimit(
+            RateLimitAction::new(&action, &service).expect("empty predicates should compile!"),
+        )
+    }
+
+    fn build_operation(grpc_call_fn_stub: GrpcCallFn, action: RuntimeAction) -> Rc<Operation> {
         Rc::new(Operation {
             state: RefCell::from(State::Pending),
             result: RefCell::new(Ok(0)),
-            service: Rc::new(Service {
-                service_type: extension_type,
-                endpoint: "local".to_string(),
-                failure_mode: FailureMode::Deny,
-                timeout: Timeout(Duration::from_millis(42)),
-            }),
-            action: Action {
-                service: "local".to_string(),
-                scope: "".to_string(),
-                predicates: vec![],
-                compiled_predicates: OnceCell::default(),
-                data: vec![],
-            },
-            service_handler: Rc::new(build_grpc_service_handler()),
+            action: Rc::new(action),
+            service_handler: build_grpc_service_handler(),
             grpc_call_fn: grpc_call_fn_stub,
             get_map_values_bytes_fn: get_map_values_bytes_fn_stub,
             grpc_message_build_fn: grpc_message_build_fn_stub,
@@ -377,17 +382,17 @@ mod tests {
 
     #[test]
     fn operation_getters() {
-        let operation = build_operation(default_grpc_call_fn_stub, ServiceType::RateLimit);
+        let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action());
 
         assert_eq!(operation.get_state(), State::Pending);
-        assert_eq!(*operation.get_service_type(), ServiceType::RateLimit);
+        assert_eq!(operation.get_service_type(), ServiceType::RateLimit);
         assert_eq!(operation.get_failure_mode(), FailureMode::Deny);
         assert_eq!(operation.get_result(), Ok(0));
     }
 
     #[test]
     fn operation_transition() {
-        let operation = build_operation(default_grpc_call_fn_stub, ServiceType::RateLimit);
+        let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action());
         assert_eq!(operation.get_result(), Ok(0));
         assert_eq!(operation.get_state(), State::Pending);
         let mut res = operation.trigger();
@@ -404,10 +409,8 @@ mod tests {
         let mut operation_dispatcher = OperationDispatcher::default();
 
         assert_eq!(operation_dispatcher.operations.len(), 0);
-        operation_dispatcher.push_operations(vec![build_operation(
-            default_grpc_call_fn_stub,
-            ServiceType::RateLimit,
-        )]);
+        let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action());
+        operation_dispatcher.push_operations(vec![operation]);
 
         assert_eq!(operation_dispatcher.operations.len(), 1);
     }
@@ -415,10 +418,8 @@ mod tests {
     #[test]
     fn operation_dispatcher_get_current_action_state() {
         let mut operation_dispatcher = OperationDispatcher::default();
-        operation_dispatcher.push_operations(vec![build_operation(
-            default_grpc_call_fn_stub,
-            ServiceType::RateLimit,
-        )]);
+        let operation = build_operation(default_grpc_call_fn_stub, build_rate_limit_grpc_action());
+        operation_dispatcher.push_operations(vec![operation]);
         assert_eq!(
             operation_dispatcher.get_current_operation_state(),
             Some(State::Pending)
@@ -452,8 +453,8 @@ mod tests {
         }
 
         operation_dispatcher.push_operations(vec![
-            build_operation(grpc_call_fn_stub_66, ServiceType::RateLimit),
-            build_operation(grpc_call_fn_stub_77, ServiceType::Auth),
+            build_operation(grpc_call_fn_stub_66, build_rate_limit_grpc_action()),
+            build_operation(grpc_call_fn_stub_77, build_auth_grpc_action()),
         ]);
 
         assert_eq!(
@@ -471,7 +472,7 @@ mod tests {
             Ok(66)
         );
         assert_eq!(
-            *op.clone()
+            op.clone()
                 .expect("ok result")
                 .expect("operation is some")
                 .get_service_type(),
@@ -509,7 +510,7 @@ mod tests {
             Ok(77)
         );
         assert_eq!(
-            *op.clone()
+            op.clone()
                 .expect("ok result")
                 .expect("operation is some")
                 .get_service_type(),
diff --git a/src/ratelimit_action.rs b/src/ratelimit_action.rs
new file mode 100644
index 0000000..d093396
--- /dev/null
+++ b/src/ratelimit_action.rs
@@ -0,0 +1,320 @@
+use crate::configuration::{Action, DataType, FailureMode, Service};
+use crate::data::Expression;
+use crate::data::Predicate;
+use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry};
+use crate::service::GrpcService;
+use cel_interpreter::Value;
+use log::error;
+use protobuf::RepeatedField;
+use std::rc::Rc;
+
+#[derive(Debug)]
+struct DescriptorEntryBuilder {
+    pub key: String,
+    pub expression: Expression,
+}
+
+impl DescriptorEntryBuilder {
+    pub fn new(data_type: &DataType) -> Result<Self, String> {
+        match data_type {
+            DataType::Static(static_item) => Ok(DescriptorEntryBuilder {
+                key: static_item.key.clone(),
+                expression: Expression::new(format!("'{}'", static_item.value).as_str())
+                    .map_err(|e| e.to_string())?,
+            }),
+            DataType::Expression(exp_item) => Ok(DescriptorEntryBuilder {
+                key: exp_item.key.clone(),
+                expression: Expression::new(&exp_item.value).map_err(|e| e.to_string())?,
+            }),
+        }
+    }
+
+    pub fn evaluate(&self) -> RateLimitDescriptor_Entry {
+        let (key, value) = (
+            self.key.clone(),
+            match self.expression.eval() {
+                Ok(value) => match value {
+                    Value::Int(n) => format!("{n}"),
+                    Value::UInt(n) => format!("{n}"),
+                    Value::Float(n) => format!("{n}"),
+                    // todo this probably should be a proper string literal!
+                    Value::String(s) => (*s).clone(),
+                    Value::Bool(b) => format!("{b}"),
+                    Value::Null => "null".to_owned(),
+                    _ => panic!("Only scalar values can be sent as data"),
+                },
+                Err(err) => {
+                    error!("Failed to evaluate {:?}: {}", self.expression, err);
+                    panic!("Err out of this!")
+                }
+            },
+        );
+        let mut descriptor_entry = RateLimitDescriptor_Entry::new();
+        descriptor_entry.set_key(key);
+        descriptor_entry.set_value(value);
+        descriptor_entry
+    }
+}
+
+#[derive(Debug)]
+struct ConditionalData {
+    pub data: Vec<DescriptorEntryBuilder>,
+    pub predicates: Vec<Predicate>,
+}
+
+impl ConditionalData {
+    pub fn new(action: &Action) -> Result<Self, String> {
+        let mut predicates = Vec::default();
+        for predicate in &action.predicates {
+            predicates.push(Predicate::new(predicate).map_err(|e| e.to_string())?);
+        }
+
+        let mut data = Vec::default();
+        for datum in &action.data {
+            data.push(DescriptorEntryBuilder::new(&datum.item)?);
+        }
+        Ok(ConditionalData { data, predicates })
+    }
+
+    fn predicates_apply(&self) -> bool {
+        let predicates = &self.predicates;
+        predicates.is_empty()
+            || predicates.iter().all(|predicate| match predicate.test() {
+                Ok(b) => b,
+                Err(err) => {
+                    error!("Failed to evaluate {:?}: {}", predicates, err);
+                    panic!("Err out of this!")
+                }
+            })
+    }
+
+    pub fn entries(&self) -> RepeatedField<RateLimitDescriptor_Entry> {
+        if !self.predicates_apply() {
+            return RepeatedField::default();
+        }
+
+        let mut entries = RepeatedField::default();
+        for entry_builder in self.data.iter() {
+            entries.push(entry_builder.evaluate());
+        }
+
+        entries
+    }
+}
+
+#[derive(Debug)]
+pub struct RateLimitAction {
+    grpc_service: Rc<GrpcService>,
+    scope: String,
+    service_name: String,
+    conditional_data_sets: Vec<ConditionalData>,
+}
+
+impl RateLimitAction {
+    pub fn new(action: &Action, service: &Service) -> Result<Self, String> {
+        Ok(Self {
+            grpc_service: Rc::new(GrpcService::new(Rc::new(service.clone()))),
+            scope: action.scope.clone(),
+            service_name: action.service.clone(),
+            conditional_data_sets: vec![ConditionalData::new(action)?],
+        })
+    }
+
+    pub fn build_descriptor(&self) -> RateLimitDescriptor {
+        let mut entries = RepeatedField::default();
+
+        for conditional_data in self.conditional_data_sets.iter() {
+            entries.extend(conditional_data.entries());
+        }
+
+        let mut res = RateLimitDescriptor::new();
+        res.set_entries(entries);
+        res
+    }
+
+    pub fn get_grpcservice(&self) -> Rc<GrpcService> {
+        Rc::clone(&self.grpc_service)
+    }
+
+    pub fn scope(&self) -> &str {
+        self.scope.as_str()
+    }
+
+    pub fn conditions_apply(&self) -> bool {
+        // For RateLimitAction conditions always apply.
+        // It is when building the descriptor that it may be empty because predicates do not
+        // evaluate to true.
+        true
+    }
+
+    pub fn get_failure_mode(&self) -> FailureMode {
+        self.grpc_service.get_failure_mode()
+    }
+
+    #[must_use]
+    pub fn merge(&mut self, other: RateLimitAction) -> Option<RateLimitAction> {
+        if self.scope == other.scope && self.service_name == other.service_name {
+            self.conditional_data_sets
+                .extend(other.conditional_data_sets);
+            return None;
+        }
+        Some(other)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::configuration::{
+        Action, DataItem, DataType, ExpressionItem, FailureMode, Service, ServiceType, StaticItem,
+        Timeout,
+    };
+
+    fn build_service() -> Service {
+        Service {
+            service_type: ServiceType::RateLimit,
+            endpoint: "some_endpoint".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        }
+    }
+
+    fn build_action(predicates: Vec<String>, data: Vec<DataItem>) -> Action {
+        Action {
+            service: "some_service".into(),
+            scope: "some_scope".into(),
+            predicates,
+            data,
+        }
+    }
+
+    #[test]
+    fn empty_predicates_do_apply() {
+        let action = build_action(Vec::default(), Vec::default());
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        assert!(rl_action.conditions_apply());
+    }
+
+    #[test]
+    fn even_with_falsy_predicates_conditions_apply() {
+        let action = build_action(vec!["false".into()], Vec::default());
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        assert!(rl_action.conditions_apply());
+    }
+
+    #[test]
+    fn empty_data_generates_empty_descriptor() {
+        let action = build_action(Vec::default(), Vec::default());
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        assert_eq!(rl_action.build_descriptor(), RateLimitDescriptor::default());
+    }
+
+    #[test]
+    fn descriptor_entry_from_expression() {
+        let data = vec![DataItem {
+            item: DataType::Expression(ExpressionItem {
+                key: "key_1".into(),
+                value: "'value_1'".into(),
+            }),
+        }];
+        let action = build_action(Vec::default(), data);
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        let descriptor = rl_action.build_descriptor();
+        assert_eq!(descriptor.get_entries().len(), 1);
+        assert_eq!(descriptor.get_entries()[0].key, String::from("key_1"));
+        assert_eq!(descriptor.get_entries()[0].value, String::from("value_1"));
+    }
+
+    #[test]
+    fn descriptor_entry_from_static() {
+        let data = vec![DataItem {
+            item: DataType::Static(StaticItem {
+                key: "key_1".into(),
+                value: "value_1".into(),
+            }),
+        }];
+        let action = build_action(Vec::default(), data);
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        let descriptor = rl_action.build_descriptor();
+        assert_eq!(descriptor.get_entries().len(), 1);
+        assert_eq!(descriptor.get_entries()[0].key, String::from("key_1"));
+        assert_eq!(descriptor.get_entries()[0].value, String::from("value_1"));
+    }
+
+    #[test]
+    fn descriptor_entries_not_generated_when_predicates_evaluate_to_false() {
+        let data = vec![DataItem {
+            item: DataType::Expression(ExpressionItem {
+                key: "key_1".into(),
+                value: "'value_1'".into(),
+            }),
+        }];
+
+        let predicates = vec!["false".into(), "true".into()];
+        let action = build_action(predicates, data);
+        let service = build_service();
+        let rl_action = RateLimitAction::new(&action, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+        assert_eq!(rl_action.build_descriptor(), RateLimitDescriptor::default());
+    }
+
+    #[test]
+    fn merged_actions_generate_descriptor_entries_for_truthy_predicates() {
+        let service = build_service();
+
+        let data_1 = vec![DataItem {
+            item: DataType::Expression(ExpressionItem {
+                key: "key_1".into(),
+                value: "'value_1'".into(),
+            }),
+        }];
+        let predicates_1 = vec!["true".into()];
+        let action_1 = build_action(predicates_1, data_1);
+        let mut rl_action_1 = RateLimitAction::new(&action_1, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        let data_2 = vec![DataItem {
+            item: DataType::Expression(ExpressionItem {
+                key: "key_2".into(),
+                value: "'value_2'".into(),
+            }),
+        }];
+        let predicates_2 = vec!["false".into()];
+        let action_2 = build_action(predicates_2, data_2);
+        let rl_action_2 = RateLimitAction::new(&action_2, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        let data_3 = vec![DataItem {
+            item: DataType::Expression(ExpressionItem {
+                key: "key_3".into(),
+                value: "'value_3'".into(),
+            }),
+        }];
+        let predicates_3 = vec!["true".into()];
+        let action_3 = build_action(predicates_3, data_3);
+        let rl_action_3 = RateLimitAction::new(&action_3, &service)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        assert!(rl_action_1.merge(rl_action_2).is_none());
+        assert!(rl_action_1.merge(rl_action_3).is_none());
+
+        // it should generate descriptor entries from action 1 and action 3
+
+        let descriptor = rl_action_1.build_descriptor();
+        assert_eq!(descriptor.get_entries().len(), 2);
+        assert_eq!(descriptor.get_entries()[0].key, String::from("key_1"));
+        assert_eq!(descriptor.get_entries()[0].value, String::from("value_1"));
+        assert_eq!(descriptor.get_entries()[1].key, String::from("key_3"));
+        assert_eq!(descriptor.get_entries()[1].value, String::from("value_3"));
+    }
+}
diff --git a/src/runtime_action.rs b/src/runtime_action.rs
new file mode 100644
index 0000000..464b04f
--- /dev/null
+++ b/src/runtime_action.rs
@@ -0,0 +1,167 @@
+use crate::auth_action::AuthAction;
+use crate::configuration::{Action, FailureMode, Service, ServiceType};
+use crate::ratelimit_action::RateLimitAction;
+use crate::service::GrpcService;
+use std::collections::HashMap;
+use std::rc::Rc;
+use std::time::Duration;
+
+#[derive(Debug)]
+pub enum RuntimeAction {
+    Auth(AuthAction),
+    RateLimit(RateLimitAction),
+}
+
+impl RuntimeAction {
+    pub fn new(action: &Action, services: &HashMap<String, Service>) -> Result<Self, String> {
+        let service = services
+            .get(&action.service)
+            .ok_or(format!("Unknown service: {}", action.service))?;
+
+        match service.service_type {
+            ServiceType::RateLimit => Ok(Self::RateLimit(RateLimitAction::new(action, service)?)),
+            ServiceType::Auth => Ok(Self::Auth(AuthAction::new(action, service)?)),
+        }
+    }
+
+    pub fn grpc_service(&self) -> Rc<GrpcService> {
+        match self {
+            Self::Auth(auth_action) => auth_action.get_grpcservice(),
+            Self::RateLimit(rl_action) => rl_action.get_grpcservice(),
+        }
+    }
+
+    pub fn conditions_apply(&self) -> bool {
+        match self {
+            Self::Auth(auth_action) => auth_action.conditions_apply(),
+            Self::RateLimit(rl_action) => rl_action.conditions_apply(),
+        }
+    }
+
+    pub fn get_failure_mode(&self) -> FailureMode {
+        match self {
+            Self::Auth(auth_action) => auth_action.get_failure_mode(),
+            Self::RateLimit(rl_action) => rl_action.get_failure_mode(),
+        }
+    }
+
+    pub fn get_timeout(&self) -> Duration {
+        self.grpc_service().get_timeout()
+    }
+
+    pub fn get_service_type(&self) -> ServiceType {
+        self.grpc_service().get_service_type()
+    }
+
+    #[must_use]
+    pub fn merge(&mut self, other: RuntimeAction) -> Option<RuntimeAction> {
+        // only makes sense for rate limiting actions
+        if let Self::RateLimit(self_rl_action) = self {
+            if let Self::RateLimit(other_rl_action) = other {
+                return self_rl_action.merge(other_rl_action).map(Self::RateLimit);
+            }
+        }
+        Some(other)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::configuration::{Action, FailureMode, ServiceType, Timeout};
+
+    fn build_rl_service() -> Service {
+        Service {
+            service_type: ServiceType::RateLimit,
+            endpoint: "limitador".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        }
+    }
+
+    fn build_auth_service() -> Service {
+        Service {
+            service_type: ServiceType::Auth,
+            endpoint: "authorino".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        }
+    }
+
+    fn build_action(service: &str, scope: &str) -> Action {
+        Action {
+            service: service.into(),
+            scope: scope.into(),
+            predicates: Vec::default(),
+            data: Vec::default(),
+        }
+    }
+
+    #[test]
+    fn only_rl_actions_are_merged() {
+        let mut services = HashMap::new();
+        services.insert(String::from("service_rl"), build_rl_service());
+
+        let rl_action_0 = build_action("service_rl", "scope");
+        let rl_action_1 = build_action("service_rl", "scope");
+
+        let mut rl_r_action_0 = RuntimeAction::new(&rl_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+        let rl_r_action_1 = RuntimeAction::new(&rl_action_1, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        assert!(rl_r_action_0.merge(rl_r_action_1).is_none());
+    }
+
+    #[test]
+    fn auth_actions_are_not_merged() {
+        let mut services = HashMap::new();
+        services.insert(String::from("service_auth"), build_auth_service());
+
+        let auth_action_0 = build_action("service_auth", "scope");
+        let auth_action_1 = build_action("service_auth", "scope");
+
+        let mut auth_r_action_0 = RuntimeAction::new(&auth_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+        let auth_r_action_1 = RuntimeAction::new(&auth_action_1, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        assert!(auth_r_action_0.merge(auth_r_action_1).is_some());
+    }
+
+    #[test]
+    fn auth_actions_do_not_merge_rl() {
+        let mut services = HashMap::new();
+        services.insert(String::from("service_rl"), build_rl_service());
+        services.insert(String::from("service_auth"), build_auth_service());
+
+        let rl_action_0 = build_action("service_rl", "scope");
+        let auth_action_0 = build_action("service_auth", "scope");
+
+        let mut rl_r_action_0 = RuntimeAction::new(&rl_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        let auth_r_action_0 = RuntimeAction::new(&auth_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        assert!(rl_r_action_0.merge(auth_r_action_0).is_some());
+    }
+
+    #[test]
+    fn rl_actions_do_not_merge_auth() {
+        let mut services = HashMap::new();
+        services.insert(String::from("service_rl"), build_rl_service());
+        services.insert(String::from("service_auth"), build_auth_service());
+
+        let rl_action_0 = build_action("service_rl", "scope");
+        let auth_action_0 = build_action("service_auth", "scope");
+
+        let rl_r_action_0 = RuntimeAction::new(&rl_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        let mut auth_r_action_0 = RuntimeAction::new(&auth_action_0, &services)
+            .expect("action building failed. Maybe predicates compilation?");
+
+        assert!(auth_r_action_0.merge(rl_r_action_0).is_some());
+    }
+}
diff --git a/src/runtime_action_set.rs b/src/runtime_action_set.rs
new file mode 100644
index 0000000..840ed6e
--- /dev/null
+++ b/src/runtime_action_set.rs
@@ -0,0 +1,259 @@
+use crate::configuration::{ActionSet, Service};
+use crate::data::Predicate;
+use crate::runtime_action::RuntimeAction;
+use log::error;
+use std::collections::HashMap;
+use std::rc::Rc;
+
+#[derive(Debug)]
+pub struct RuntimeActionSet {
+    pub name: String,
+    pub route_rule_predicates: Vec<Predicate>,
+    pub runtime_actions: Vec<Rc<RuntimeAction>>,
+}
+
+impl RuntimeActionSet {
+    pub fn new(
+        action_set: &ActionSet,
+        services: &HashMap<String, Service>,
+    ) -> Result<Self, String> {
+        // route predicates
+        let mut route_rule_predicates = Vec::default();
+        for predicate in &action_set.route_rule_conditions.predicates {
+            route_rule_predicates
+                .push(Predicate::route_rule(predicate).map_err(|e| e.to_string())?);
+        }
+
+        // actions
+        let mut all_runtime_actions = Vec::default();
+        for action in action_set.actions.iter() {
+            all_runtime_actions.push(RuntimeAction::new(action, services)?);
+        }
+        let runtime_actions = Self::merge_subsequent_actions_of_a_kind(all_runtime_actions);
+
+        Ok(Self {
+            name: action_set.name.clone(),
+            route_rule_predicates,
+            runtime_actions: runtime_actions.into_iter().map(Rc::new).collect(),
+        })
+    }
+
+    fn merge_subsequent_actions_of_a_kind(
+        runtime_actions: Vec<RuntimeAction>,
+    ) -> Vec<RuntimeAction> {
+        // fold subsequent actions of a kind (kind being defined in the action)
+        let mut folded_actions: Vec<RuntimeAction> = Vec::default();
+        for r_action in runtime_actions {
+            match folded_actions.last_mut() {
+                Some(existing) => {
+                    if let Some(action) = existing.merge(r_action) {
+                        folded_actions.push(action);
+                    }
+                }
+                None => folded_actions.push(r_action),
+            }
+        }
+        folded_actions
+    }
+
+    pub fn conditions_apply(&self) -> bool {
+        let predicates = &self.route_rule_predicates;
+        predicates.is_empty()
+            || predicates.iter().all(|predicate| match predicate.test() {
+                Ok(b) => b,
+                Err(err) => {
+                    error!("Failed to evaluate {:?}: {}", predicate, err);
+                    panic!("Err out of this!")
+                }
+            })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::configuration::{
+        Action, ActionSet, FailureMode, RouteRuleConditions, ServiceType, Timeout,
+    };
+
+    #[test]
+    fn empty_route_rule_predicates_do_apply() {
+        let action_set = ActionSet::new("some_name".to_owned(), Default::default(), Vec::new());
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &HashMap::default())
+            .expect("should not happen from an empty set of actions");
+
+        assert!(runtime_action_set.conditions_apply())
+    }
+
+    #[test]
+    fn when_all_predicates_are_truthy_conditions_apply() {
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            RouteRuleConditions {
+                hostnames: Vec::default(),
+                predicates: vec!["true".into(), "true".into()],
+            },
+            Vec::new(),
+        );
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &HashMap::default())
+            .expect("should not happen from an empty set of actions");
+
+        assert!(runtime_action_set.conditions_apply())
+    }
+
+    #[test]
+    fn when_not_all_predicates_are_truthy_action_does_not_apply() {
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            RouteRuleConditions {
+                hostnames: Vec::default(),
+                predicates: vec!["true".into(), "true".into(), "true".into(), "false".into()],
+            },
+            Vec::new(),
+        );
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &HashMap::default())
+            .expect("should not happen from an empty set of actions");
+
+        assert!(!runtime_action_set.conditions_apply())
+    }
+
+    #[test]
+    #[should_panic]
+    fn when_a_cel_expression_does_not_evaluate_to_bool_panics() {
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            RouteRuleConditions {
+                hostnames: Vec::default(),
+                predicates: vec!["true".into(), "true".into(), "true".into(), "1".into()],
+            },
+            Vec::new(),
+        );
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &HashMap::default())
+            .expect("should not happen from an empty set of actions");
+        runtime_action_set.conditions_apply();
+    }
+
+    fn build_rl_service() -> Service {
+        Service {
+            service_type: ServiceType::RateLimit,
+            endpoint: "limitador".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        }
+    }
+
+    fn build_auth_service() -> Service {
+        Service {
+            service_type: ServiceType::Auth,
+            endpoint: "authorino".into(),
+            failure_mode: FailureMode::default(),
+            timeout: Timeout::default(),
+        }
+    }
+
+    fn build_action(service: &str, scope: &str) -> Action {
+        Action {
+            service: service.into(),
+            scope: scope.into(),
+            predicates: Vec::default(),
+            data: Vec::default(),
+        }
+    }
+
+    #[test]
+    fn simple_folding() {
+        let action_a = build_action("rl_service_common", "scope_common");
+        let action_b = build_action("rl_service_common", "scope_common");
+
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            Default::default(),
+            vec![action_a, action_b],
+        );
+
+        let mut services = HashMap::new();
+        services.insert(String::from("rl_service_common"), build_rl_service());
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &services)
+            .expect("should not happen for simple actions");
+
+        assert_eq!(runtime_action_set.runtime_actions.len(), 1);
+    }
+
+    #[test]
+    fn unrelated_actions_by_kind_are_not_folded() {
+        let red_action_0 = build_action("service_red", "scope_red");
+        let blue_action_1 = build_action("service_blue", "scope_blue");
+
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            Default::default(),
+            vec![red_action_0, blue_action_1],
+        );
+
+        let mut services = HashMap::new();
+        services.insert(String::from("service_red"), build_rl_service());
+        services.insert(String::from("service_blue"), build_auth_service());
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &services)
+            .expect("should not happen from simple actions");
+
+        assert_eq!(runtime_action_set.runtime_actions.len(), 2);
+    }
+
+    #[test]
+    fn unrelated_rl_actions_are_not_folded() {
+        let red_action_0 = build_action("service_red", "scope_red");
+        let blue_action_1 = build_action("service_blue", "scope_blue");
+        let green_action_2 = build_action("service_green", "scope_green");
+
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            Default::default(),
+            vec![red_action_0, blue_action_1, green_action_2],
+        );
+
+        let mut services = HashMap::new();
+        services.insert(String::from("service_red"), build_rl_service());
+        services.insert(String::from("service_blue"), build_rl_service());
+        services.insert(String::from("service_green"), build_rl_service());
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &services)
+            .expect("should not happen from simple actions");
+
+        assert_eq!(runtime_action_set.runtime_actions.len(), 3);
+    }
+
+    #[test]
+    fn only_subsequent_actions_are_folded() {
+        let red_action_0 = build_action("service_red", "common");
+        let red_action_1 = build_action("service_red", "common");
+        let blue_action_2 = build_action("service_blue", "common");
+        let red_action_3 = build_action("service_red", "common");
+        let red_action_4 = build_action("service_red", "common");
+
+        let action_set = ActionSet::new(
+            "some_name".to_owned(),
+            Default::default(),
+            vec![
+                red_action_0,
+                red_action_1,
+                blue_action_2,
+                red_action_3,
+                red_action_4,
+            ],
+        );
+
+        let mut services = HashMap::new();
+        services.insert(String::from("service_red"), build_rl_service());
+        services.insert(String::from("service_blue"), build_rl_service());
+
+        let runtime_action_set = RuntimeActionSet::new(&action_set, &services)
+            .expect("should not happen from simple actions");
+
+        assert_eq!(runtime_action_set.runtime_actions.len(), 3);
+    }
+}
diff --git a/src/runtime_config.rs b/src/runtime_config.rs
new file mode 100644
index 0000000..25c63cb
--- /dev/null
+++ b/src/runtime_config.rs
@@ -0,0 +1,157 @@
+use crate::action_set_index::ActionSetIndex;
+use crate::configuration::PluginConfiguration;
+use crate::runtime_action_set::RuntimeActionSet;
+use std::rc::Rc;
+
+pub(crate) struct RuntimeConfig {
+    pub index: ActionSetIndex,
+}
+
+impl TryFrom<PluginConfiguration> for RuntimeConfig {
+    type Error = String;
+
+    fn try_from(config: PluginConfiguration) -> Result<Self, Self::Error> {
+        let mut index = ActionSetIndex::new();
+        for action_set in config.action_sets.iter() {
+            let runtime_action_set = Rc::new(RuntimeActionSet::new(action_set, &config.services)?);
+            for hostname in action_set.route_rule_conditions.hostnames.iter() {
+                index.insert(hostname, Rc::clone(&runtime_action_set));
+            }
+        }
+
+        Ok(Self { index })
+    }
+}
+
+impl Default for RuntimeConfig {
+    fn default() -> Self {
+        Self {
+            index: ActionSetIndex::new(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const CONFIG: &str = r#"{
+        "services": {
+            "authorino": {
+                "type": "auth",
+                "endpoint": "authorino-cluster",
+                "failureMode": "deny",
+                "timeout": "24ms"
+            },
+            "limitador": {
+                "type": "ratelimit",
+                "endpoint": "limitador-cluster",
+                "failureMode": "allow",
+                "timeout": "42ms"
+            }
+        },
+        "actionSets": [
+        {
+            "name": "rlp-ns-A/rlp-name-A",
+            "routeRuleConditions": {
+                "hostnames": ["*.toystore.com", "example.com"],
+                "predicates": [
+                    "request.path == '/admin/toy'",
+                    "request.method == 'POST'",
+                    "request.host == 'cars.toystore.com'"
+                ]
+            },
+            "actions": [
+            {
+                "service": "authorino",
+                "scope": "authconfig-A"
+            },
+            {
+                "service": "limitador",
+                "scope": "rlp-ns-A/rlp-name-A",
+                "predicates": [
+                    "auth.metadata.username == 'alice'"
+                ],
+                "data": [
+                {
+                    "static": {
+                        "key": "rlp-ns-A/rlp-name-A",
+                        "value": "1"
+                    }
+                },
+                {
+                    "expression": {
+                        "key": "username",
+                        "value": "auth.metadata.username"
+                    }
+                }]
+            }]
+        }]
+    }"#;
+
+    #[test]
+    fn runtime_config_from_configuration() {
+        let res = serde_json::from_str::<PluginConfiguration>(CONFIG);
+        if let Err(ref e) = res {
+            eprintln!("{e}");
+        }
+        assert!(res.is_ok());
+
+        let result = RuntimeConfig::try_from(res.unwrap());
+        let runtime_config = result.expect("That didn't work");
+        let rlp_option = runtime_config
+            .index
+            .get_longest_match_action_sets("example.com");
+        assert!(rlp_option.is_some());
+
+        let rlp_option = runtime_config
+            .index
+            .get_longest_match_action_sets("test.toystore.com");
+        assert!(rlp_option.is_some());
+
+        let rlp_option = runtime_config
+            .index
+            .get_longest_match_action_sets("unknown");
+        assert!(rlp_option.is_none());
+    }
+
+    #[test]
+    fn runtime_config_raises_error_when_action_service_does_not_exist_in_services() {
+        let config = r#"{
+            "services": {
+                "limitador": {
+                    "type": "ratelimit",
+                    "endpoint": "limitador",
+                    "failureMode": "allow"
+                }
+            },
+            "actionSets": [
+            {
+                "name": "some-name",
+                "routeRuleConditions": {
+                    "hostnames": ["*.example.com"]
+                },
+                "actions": [
+                {
+                    "service": "unknown",
+                    "scope": "some-scope",
+                    "data": [
+                    {
+                        "expression": {
+                            "key": "a",
+                            "value": "1"
+                        }
+                    }]
+                }]
+            }]
+        }"#;
+        let serde_res = serde_json::from_str::<PluginConfiguration>(config);
+        if let Err(ref e) = serde_res {
+            eprintln!("{e}");
+        }
+        assert!(serde_res.is_ok());
+
+        let result = RuntimeConfig::try_from(serde_res.expect("That didn't work"));
+        assert_eq!(result.err(), Some("Unknown service: unknown".into()));
+    }
+}
diff --git a/src/service.rs b/src/service.rs
index a981add..461ecc2 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -2,10 +2,10 @@ pub(crate) mod auth;
 pub(crate) mod grpc_message;
 pub(crate) mod rate_limit;
 
-use crate::configuration::action::Action;
 use crate::configuration::{FailureMode, Service, ServiceType};
 use crate::envoy::StatusCode;
 use crate::operation_dispatcher::Operation;
+use crate::runtime_action::RuntimeAction;
 use crate::service::auth::{AuthService, AUTH_METHOD_NAME, AUTH_SERVICE_NAME};
 use crate::service::grpc_message::{GrpcMessageRequest, GrpcMessageResponse};
 use crate::service::rate_limit::{RateLimitService, RATELIMIT_METHOD_NAME, RATELIMIT_SERVICE_NAME};
@@ -42,6 +42,18 @@ impl GrpcService {
         }
     }
 
+    pub fn get_timeout(&self) -> Duration {
+        self.service.timeout.0
+    }
+
+    pub fn get_service_type(&self) -> ServiceType {
+        self.service.service_type.clone()
+    }
+
+    pub fn get_failure_mode(&self) -> FailureMode {
+        self.service.failure_mode
+    }
+
     fn endpoint(&self) -> &str {
         &self.service.endpoint
     }
@@ -60,7 +72,7 @@ impl GrpcService {
         if let Ok(Some(res_body_bytes)) =
             hostcalls::get_buffer(BufferType::GrpcReceiveBuffer, 0, resp_size)
         {
-            match GrpcMessageResponse::new(operation.get_service_type(), &res_body_bytes) {
+            match GrpcMessageResponse::new(&operation.get_service_type(), &res_body_bytes) {
                 Ok(res) => match operation.get_service_type() {
                     ServiceType::Auth => AuthService::process_auth_grpc_response(res, failure_mode),
                     ServiceType::RateLimit => {
@@ -120,8 +132,8 @@ pub type GrpcCallFn = fn(
 
 pub type GetMapValuesBytesFn = fn(map_type: MapType, key: &str) -> Result<Option<Bytes>, Status>;
 
-pub type GrpcMessageBuildFn =
-    fn(service_type: &ServiceType, action: &Action) -> Option<GrpcMessageRequest>;
+pub type GrpcMessageBuildFn = fn(action: &RuntimeAction) -> Option<GrpcMessageRequest>;
+
 #[derive(Debug)]
 pub struct GrpcServiceHandler {
     grpc_service: Rc<GrpcService>,
@@ -163,11 +175,8 @@ impl GrpcServiceHandler {
             timeout,
         )
     }
-
-    pub fn get_service(&self) -> Rc<Service> {
-        Rc::clone(&self.grpc_service.service)
-    }
 }
+
 #[derive(Debug)]
 pub struct HeaderResolver {
     headers: OnceCell<Vec<(&'static str, Bytes)>>,
diff --git a/src/service/grpc_message.rs b/src/service/grpc_message.rs
index 68daffd..0014831 100644
--- a/src/service/grpc_message.rs
+++ b/src/service/grpc_message.rs
@@ -1,6 +1,6 @@
-use crate::configuration::action::Action;
 use crate::configuration::ServiceType;
 use crate::envoy::{CheckRequest, CheckResponse, RateLimitRequest, RateLimitResponse};
+use crate::runtime_action::RuntimeAction;
 use crate::service::auth::AuthService;
 use crate::service::rate_limit::RateLimitService;
 use log::debug;
@@ -124,22 +124,25 @@ impl Message for GrpcMessageRequest {
 
 impl GrpcMessageRequest {
     // Using domain as ce_host for the time being, we might pass a DataType in the future.
-    pub fn new(service_type: &ServiceType, action: &Action) -> Option<Self> {
-        match service_type {
-            ServiceType::RateLimit => {
-                let descriptors = action.build_descriptors();
-                if descriptors.is_empty() {
+    pub fn new(action: &RuntimeAction) -> Option<Self> {
+        match action {
+            RuntimeAction::RateLimit(rl_action) => {
+                let descriptor = rl_action.build_descriptor();
+                if descriptor.entries.is_empty() {
                     debug!("grpc_message_request: empty descriptors");
                     None
                 } else {
                     Some(GrpcMessageRequest::RateLimit(
-                        RateLimitService::request_message(action.scope.clone(), descriptors),
+                        RateLimitService::request_message(
+                            String::from(rl_action.scope()),
+                            vec![descriptor].into(),
+                        ),
                     ))
                 }
             }
-            ServiceType::Auth => Some(GrpcMessageRequest::Auth(AuthService::request_message(
-                action.scope.clone(),
-            ))),
+            RuntimeAction::Auth(auth_action) => Some(GrpcMessageRequest::Auth(
+                AuthService::request_message(String::from(auth_action.scope())),
+            )),
         }
     }
 }
diff --git a/tests/auth.rs b/tests/auth.rs
index 38b6081..d0ded73 100644
--- a/tests/auth.rs
+++ b/tests/auth.rs
@@ -182,10 +182,10 @@ fn it_auths() {
                 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
                 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
                 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98,
-                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7,
-                58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97,
-                116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117,
-                101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10,
+                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5,
+                58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101,
+                113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116,
+                104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 34, 10,
                 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116,
                 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82,
                 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97,
@@ -379,10 +379,10 @@ fn it_denies() {
                 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
                 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
                 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98,
-                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7,
-                58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97,
-                116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117,
-                101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10,
+                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5,
+                58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101,
+                113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116,
+                104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 34, 10,
                 47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116,
                 111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82,
                 8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97,
@@ -436,3 +436,317 @@ fn it_denies() {
         .execute_and_expect(ReturnType::Action(Action::Continue))
         .unwrap();
 }
+
+#[test]
+#[serial]
+fn it_does_not_fold_auth_actions() {
+    let args = tester::MockSettings {
+        wasm_path: wasm_module(),
+        quiet: false,
+        allow_unexpected: false,
+    };
+    let mut module = tester::mock(args).unwrap();
+
+    module
+        .call_start()
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    let root_context = 1;
+    let cfg = r#"{
+        "services": {
+            "auth": {
+                "type": "auth",
+                "endpoint": "authorino-cluster",
+                "failureMode": "deny",
+                "timeout": "5s"
+            }
+        },
+        "actionSets": [
+        {
+            "name": "some-name",
+            "routeRuleConditions": {
+                "hostnames": ["*.com"]
+            },
+            "actions": [
+            {
+                "service": "auth",
+                "scope": "auth-scope",
+                "predicates" : []
+            },
+            {
+                "service": "auth",
+                "scope": "auth-scope",
+                "predicates" : []
+            }]
+        }]
+    }"#;
+
+    module
+        .call_proxy_on_context_create(root_context, 0)
+        .expect_log(Some(LogLevel::Info), Some("#1 set_root_context"))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    module
+        .call_proxy_on_configure(root_context, 0)
+        .expect_log(Some(LogLevel::Info), Some("#1 on_configure"))
+        .expect_get_buffer_bytes(Some(BufferType::PluginConfiguration))
+        .returning(Some(cfg.as_bytes()))
+        .expect_log(Some(LogLevel::Info), None)
+        .execute_and_expect(ReturnType::Bool(true))
+        .unwrap();
+
+    let http_context = 2;
+    module
+        .call_proxy_on_context_create(http_context, root_context)
+        .expect_log(Some(LogLevel::Debug), Some("#2 create_http_context"))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    module
+        .call_proxy_on_request_headers(http_context, 0, false)
+        .expect_log(Some(LogLevel::Debug), Some("#2 on_http_request_headers"))
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some(":authority"))
+        .returning(Some("example.com"))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 action_set selected some-name"),
+        )
+        // retrieving properties for CheckRequest
+        .expect_get_header_map_pairs(Some(MapType::HttpRequestHeaders))
+        .returning(None)
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"host\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "host"]))
+        .returning(Some(data::request::HOST))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"method\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "method"]))
+        .returning(Some(data::request::method::GET))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"scheme\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "scheme"]))
+        .returning(Some(data::request::scheme::HTTP))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"path\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "path"]))
+        .returning(Some(data::request::path::ADMIN_TOY))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"protocol\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "protocol"]))
+        .returning(Some(data::request::protocol::HTTP_1_1))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"time\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "time"]))
+        .returning(Some(data::request::TIME))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"destination\", \"address\"]"),
+        )
+        .expect_get_property(Some(vec!["destination", "address"]))
+        .returning(Some(data::destination::ADDRESS))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"destination\", \"port\"]"),
+        )
+        .expect_get_property(Some(vec!["destination", "port"]))
+        .returning(Some(data::destination::port::P_8000))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"source\", \"address\"]"),
+        )
+        .expect_get_property(Some(vec!["source", "address"]))
+        .returning(Some(data::source::ADDRESS))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"source\", \"port\"]"),
+        )
+        .expect_get_property(Some(vec!["source", "port"]))
+        .returning(Some(data::source::port::P_45000))
+        // retrieving tracing headers
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent"))
+        .returning(None)
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("tracestate"))
+        .returning(None)
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("baggage"))
+        .returning(None)
+        .expect_grpc_call(
+            Some("authorino-cluster"),
+            Some("envoy.service.auth.v3.Authorization"),
+            Some("Check"),
+            Some(&[0, 0, 0, 0]),
+            Some(&[
+                10, 234, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58,
+                52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55,
+                46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
+                146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
+                26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5,
+                58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101,
+                113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116,
+                104, 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97,
+                98, 105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 34, 10, 47,
+                97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111,
+                121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8,
+                72, 84, 84, 80, 47, 49, 46, 49, 82, 18, 10, 4, 104, 111, 115, 116, 18, 10, 97, 117,
+                116, 104, 45, 115, 99, 111, 112, 101, 90, 0,
+            ]),
+            Some(5000),
+        )
+        .returning(Some(42))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 initiated gRPC call (id# 42)"),
+        )
+        .execute_and_expect(ReturnType::Action(Action::Pause))
+        .unwrap();
+
+    // TODO: response containing dynamic metadata
+    // set_property is panicking with proxy-wasm-test-framework
+    // because the `expect_set_property` is not yet implemented neither on original repo nor our fork
+    // let grpc_response: [u8; 41] = [
+    //     10, 0, 34, 35, 10, 33, 10, 8, 105, 100, 101, 110, 116, 105, 116, 121, 18, 21, 42, 19, 10,
+    //     17, 10, 6, 117, 115, 101, 114, 105, 100, 18, 7, 26, 5, 97, 108, 105, 99, 101, 26, 0,
+    // ];
+    let grpc_response: [u8; 6] = [10, 0, 34, 0, 26, 0];
+    module
+        .call_proxy_on_grpc_receive(http_context, 42, grpc_response.len() as i32)
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 on_grpc_call_response: received gRPC call response: token: 42, status: 0"),
+        )
+        .expect_get_buffer_bytes(Some(BufferType::GrpcReceiveBuffer))
+        .returning(Some(&grpc_response))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("process_auth_grpc_response: received OkHttpResponse"),
+        )
+        .expect_get_header_map_pairs(Some(MapType::HttpRequestHeaders))
+        .returning(None)
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"host\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "host"]))
+        .returning(Some(data::request::HOST))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"method\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "method"]))
+        .returning(Some(data::request::method::GET))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"scheme\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "scheme"]))
+        .returning(Some(data::request::scheme::HTTP))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"path\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "path"]))
+        .returning(Some(data::request::path::ADMIN_TOY))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"protocol\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "protocol"]))
+        .returning(Some(data::request::protocol::HTTP_1_1))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"request\", \"time\"]"),
+        )
+        .expect_get_property(Some(vec!["request", "time"]))
+        .returning(Some(data::request::TIME))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"destination\", \"address\"]"),
+        )
+        .expect_get_property(Some(vec!["destination", "address"]))
+        .returning(Some(data::destination::ADDRESS))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"destination\", \"port\"]"),
+        )
+        .expect_get_property(Some(vec!["destination", "port"]))
+        .returning(Some(data::destination::port::P_8000))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"source\", \"address\"]"),
+        )
+        .expect_get_property(Some(vec!["source", "address"]))
+        .returning(Some(data::source::ADDRESS))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("get_property: path: [\"source\", \"port\"]"),
+        )
+        .expect_get_property(Some(vec!["source", "port"]))
+        .returning(Some(data::source::port::P_45000))
+        .expect_grpc_call(
+            Some("authorino-cluster"),
+            Some("envoy.service.auth.v3.Authorization"),
+            Some("Check"),
+            Some(&[0, 0, 0, 0]),
+            Some(&[
+                10, 234, 1, 10, 25, 10, 23, 10, 21, 18, 15, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58,
+                52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55,
+                46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
+                146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
+                26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10,
+                58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101,
+                115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116,
+                104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101,
+                115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47,
+                97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111,
+                121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8,
+                72, 84, 84, 80, 47, 49, 46, 49, 82, 18, 10, 4, 104, 111, 115, 116, 18, 10, 97, 117,
+                116, 104, 45, 115, 99, 111, 112, 101, 90, 0,
+            ]),
+            Some(5000),
+        )
+        .returning(Some(42))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    // TODO: response containing dynamic metadata
+    // set_property is panicking with proxy-wasm-test-framework
+    // because the `expect_set_property` is not yet implemented neither on original repo nor our fork
+    // let grpc_response: [u8; 41] = [
+    //     10, 0, 34, 35, 10, 33, 10, 8, 105, 100, 101, 110, 116, 105, 116, 121, 18, 21, 42, 19, 10,
+    //     17, 10, 6, 117, 115, 101, 114, 105, 100, 18, 7, 26, 5, 97, 108, 105, 99, 101, 26, 0,
+    // ];
+    let grpc_response: [u8; 6] = [10, 0, 34, 0, 26, 0];
+    module
+        .call_proxy_on_grpc_receive(http_context, 42, grpc_response.len() as i32)
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 on_grpc_call_response: received gRPC call response: token: 42, status: 0"),
+        )
+        .expect_get_buffer_bytes(Some(BufferType::GrpcReceiveBuffer))
+        .returning(Some(&grpc_response))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("process_auth_grpc_response: received OkHttpResponse"),
+        )
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    module
+        .call_proxy_on_response_headers(http_context, 0, false)
+        .expect_log(Some(LogLevel::Debug), Some("#2 on_http_response_headers"))
+        .execute_and_expect(ReturnType::Action(Action::Continue))
+        .unwrap();
+}
diff --git a/tests/multi.rs b/tests/multi.rs
index bc5b6e4..20a11bf 100644
--- a/tests/multi.rs
+++ b/tests/multi.rs
@@ -198,15 +198,15 @@ fn it_performs_authenticated_rate_limiting() {
                 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55,
                 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
                 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
-                26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98,
-                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7,
-                58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97,
-                116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117,
-                101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10,
-                47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116,
-                111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82,
-                8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97,
-                117, 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0,
+                26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10,
+                58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101,
+                115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116,
+                104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101,
+                115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47,
+                97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111,
+                121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8,
+                72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117,
+                116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0,
             ]),
             Some(5000),
         )
@@ -412,15 +412,15 @@ fn unauthenticated_does_not_ratelimit() {
                 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55,
                 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
                 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
-                26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98,
-                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 14, 10, 7,
-                58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 38, 10, 5, 58, 112, 97,
-                116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117,
-                101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10,
-                47, 97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116,
-                111, 121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82,
-                8, 72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97,
-                117, 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0,
+                26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10,
+                58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101,
+                115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116,
+                104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101,
+                115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47,
+                97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111,
+                121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8,
+                72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117,
+                116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0,
             ]),
             Some(5000),
         )
@@ -686,14 +686,14 @@ fn authenticated_one_ratelimit_action_matches() {
                 52, 53, 48, 48, 48, 24, 200, 223, 2, 18, 23, 10, 21, 10, 19, 18, 14, 49, 50, 55,
                 46, 48, 46, 48, 46, 49, 58, 56, 48, 48, 48, 24, 192, 62, 34, 157, 1, 10, 12, 8,
                 146, 140, 179, 185, 6, 16, 240, 213, 233, 163, 3, 18, 140, 1, 18, 3, 71, 69, 84,
-                26, 38, 10, 5, 58, 112, 97, 116, 104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116,
-                47, 114, 101, 113, 117, 101, 115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47,
-                112, 97, 116, 104, 26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69,
-                84, 26, 30, 10, 10, 58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98,
-                105, 95, 116, 101, 115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 34, 10, 47, 97,
-                100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111, 121,
-                115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8, 72,
-                84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117,
+                26, 14, 10, 7, 58, 109, 101, 116, 104, 111, 100, 18, 3, 71, 69, 84, 26, 30, 10, 10,
+                58, 97, 117, 116, 104, 111, 114, 105, 116, 121, 18, 16, 97, 98, 105, 95, 116, 101,
+                115, 116, 95, 104, 97, 114, 110, 101, 115, 115, 26, 38, 10, 5, 58, 112, 97, 116,
+                104, 18, 29, 47, 100, 101, 102, 97, 117, 108, 116, 47, 114, 101, 113, 117, 101,
+                115, 116, 47, 104, 101, 97, 100, 101, 114, 115, 47, 112, 97, 116, 104, 34, 10, 47,
+                97, 100, 109, 105, 110, 47, 116, 111, 121, 42, 17, 99, 97, 114, 115, 46, 116, 111,
+                121, 115, 116, 111, 114, 101, 46, 99, 111, 109, 50, 4, 104, 116, 116, 112, 82, 8,
+                72, 84, 84, 80, 47, 49, 46, 49, 82, 20, 10, 4, 104, 111, 115, 116, 18, 12, 97, 117,
                 116, 104, 99, 111, 110, 102, 105, 103, 45, 65, 90, 0,
             ]),
             Some(5000),
@@ -726,10 +726,6 @@ fn authenticated_one_ratelimit_action_matches() {
         )
         .expect_get_property(Some(vec!["source", "address"]))
         .returning(Some("1.2.3.4:80".as_bytes()))
-        .expect_log(
-            Some(LogLevel::Debug),
-            Some("actions conditions do not apply, skipping"),
-        )
         .expect_log(
             Some(LogLevel::Debug),
             Some("get_property: path: [\"source\", \"address\"]"),
diff --git a/tests/rate_limited.rs b/tests/rate_limited.rs
index b88daa6..1586160 100644
--- a/tests/rate_limited.rs
+++ b/tests/rate_limited.rs
@@ -577,7 +577,7 @@ fn it_does_not_rate_limits_when_predicates_does_not_match() {
         .returning(Some(data::request::path::ADMIN))
         .expect_log(
             Some(LogLevel::Debug),
-            Some("actions conditions do not apply, skipping"),
+            Some("grpc_message_request: empty descriptors"),
         )
         .execute_and_expect(ReturnType::Action(Action::Continue))
         .unwrap();
@@ -588,3 +588,152 @@ fn it_does_not_rate_limits_when_predicates_does_not_match() {
         .execute_and_expect(ReturnType::Action(Action::Continue))
         .unwrap();
 }
+
+#[test]
+#[serial]
+fn it_folds_subsequent_actions_to_limitador_into_a_single_one() {
+    let args = tester::MockSettings {
+        wasm_path: wasm_module(),
+        quiet: false,
+        allow_unexpected: false,
+    };
+    let mut module = tester::mock(args).unwrap();
+
+    module
+        .call_start()
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    let root_context = 1;
+    let cfg = r#"{
+        "services": {
+            "limitador": {
+                "type": "ratelimit",
+                "endpoint": "limitador-cluster",
+                "failureMode": "allow",
+                "timeout": "5s"
+            }
+        },
+        "actionSets": [
+        {
+            "name": "some-name",
+            "routeRuleConditions": {
+                "hostnames": ["*.example.com"]
+            },
+            "actions": [
+            {
+                "service": "limitador",
+                "scope": "RLS-domain",
+                "data": [
+                    {
+                        "expression": {
+                            "key": "key_1",
+                            "value": "'value_1'"
+                        }
+                    }
+                ]
+            },
+            {
+                "service": "limitador",
+                "scope": "RLS-domain",
+                "data": [
+                    {
+                        "expression": {
+                            "key": "key_2",
+                            "value": "'value_2'"
+                        }
+                    }
+                ]
+            },
+            {
+                "service": "limitador",
+                "scope": "RLS-domain",
+                "data": [
+                    {
+                        "expression": {
+                            "key": "key_3",
+                            "value": "'value_3'"
+                        }
+                    }
+                ]
+            }
+            ]
+        }]
+    }"#;
+
+    module
+        .call_proxy_on_context_create(root_context, 0)
+        .expect_log(Some(LogLevel::Info), Some("#1 set_root_context"))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+    module
+        .call_proxy_on_configure(root_context, 0)
+        .expect_log(Some(LogLevel::Info), Some("#1 on_configure"))
+        .expect_get_buffer_bytes(Some(BufferType::PluginConfiguration))
+        .returning(Some(cfg.as_bytes()))
+        .expect_log(Some(LogLevel::Info), None)
+        .execute_and_expect(ReturnType::Bool(true))
+        .unwrap();
+
+    let http_context = 2;
+    module
+        .call_proxy_on_context_create(http_context, root_context)
+        .expect_log(Some(LogLevel::Debug), Some("#2 create_http_context"))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    module
+        .call_proxy_on_request_headers(http_context, 0, false)
+        .expect_log(Some(LogLevel::Debug), Some("#2 on_http_request_headers"))
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some(":authority"))
+        .returning(Some("cars.example.com"))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 action_set selected some-name"),
+        )
+        // retrieving tracing headers
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("traceparent"))
+        .returning(None)
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("tracestate"))
+        .returning(None)
+        .expect_get_header_map_value(Some(MapType::HttpRequestHeaders), Some("baggage"))
+        .returning(None)
+        .expect_grpc_call(
+            Some("limitador-cluster"),
+            Some("envoy.service.ratelimit.v3.RateLimitService"),
+            Some("ShouldRateLimit"),
+            Some(&[0, 0, 0, 0]),
+            Some(&[
+                10, 10, 82, 76, 83, 45, 100, 111, 109, 97, 105, 110, 18, 54, 10, 16, 10, 5, 107,
+                101, 121, 95, 49, 18, 7, 118, 97, 108, 117, 101, 95, 49, 10, 16, 10, 5, 107, 101,
+                121, 95, 50, 18, 7, 118, 97, 108, 117, 101, 95, 50, 10, 16, 10, 5, 107, 101, 121,
+                95, 51, 18, 7, 118, 97, 108, 117, 101, 95, 51, 24, 1,
+            ]),
+            Some(5000),
+        )
+        .returning(Some(42))
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 initiated gRPC call (id# 42)"),
+        )
+        .execute_and_expect(ReturnType::Action(Action::Pause))
+        .unwrap();
+
+    let grpc_response: [u8; 2] = [8, 1];
+    module
+        .call_proxy_on_grpc_receive(http_context, 42, grpc_response.len() as i32)
+        .expect_log(
+            Some(LogLevel::Debug),
+            Some("#2 on_grpc_call_response: received gRPC call response: token: 42, status: 0"),
+        )
+        .expect_get_buffer_bytes(Some(BufferType::GrpcReceiveBuffer))
+        .returning(Some(&grpc_response))
+        .execute_and_expect(ReturnType::None)
+        .unwrap();
+
+    module
+        .call_proxy_on_response_headers(http_context, 0, false)
+        .expect_log(Some(LogLevel::Debug), Some("#2 on_http_response_headers"))
+        .execute_and_expect(ReturnType::Action(Action::Continue))
+        .unwrap();
+}