diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index dfe7552ce5204d33b24a65c010508505ae79c018..faa282f7895534a7a5234f04d95b9298c35aea17 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -31,8 +31,14 @@ libc = "0.2"
 lazy_static = "^1"
 thiserror = "^1"
 
-smallvec = { version = "^1", default-features = false, features = ["serde"] }
 hashbrown = { version = "^0.13", features = ["serde"] }
+smallvec = { version = "^1", default-features = false, features = [
+    "serde",
+    "write",
+    "union",
+    "const_generics",
+    "const_new",
+] }
 
 toml = { version = "^0.5", features = ["preserve_order"] }
 serde = { version = "^1", default-features = false, features = [
diff --git a/src/rust/amalib/src/connexion.rs b/src/rust/amalib/src/connexion.rs
index 1e2f9bdc1ce08603e75243e6ed459b4738688852..e202f2856a5054a352fe028ab15c1720825139c1 100644
--- a/src/rust/amalib/src/connexion.rs
+++ b/src/rust/amalib/src/connexion.rs
@@ -1,3 +1,5 @@
+//! Connexion to the lektord server.
+
 use crate::*;
 use tokio::net::TcpStream;
 
diff --git a/src/rust/amalib/src/constants.rs b/src/rust/amalib/src/constants.rs
index 00977f31d71660bdbccea92318689d569645ea08..2d31b15f9e2edd07558feb70b967bfb31d7578f0 100644
--- a/src/rust/amalib/src/constants.rs
+++ b/src/rust/amalib/src/constants.rs
@@ -1,9 +1,8 @@
 //! Contains standard constants from lektord, must be updated manually if they
 //! change in lektord...
 
-#![allow(dead_code)]
-
 /// MPD commands are at most 32 character long.
+#[allow(dead_code)]
 pub(crate) const LKT_COMMAND_LEN_MAX: usize = 32;
 
 /// At most 32 words in a command are supported.
@@ -13,9 +12,11 @@ pub(crate) const LKT_MESSAGE_ARGS_MAX: usize = 32;
 pub(crate) const LKT_MESSAGE_MAX: usize = 2048;
 
 /// At most 64 commands per client.
+#[allow(dead_code)]
 pub(crate) const COMMAND_LIST_MAX: usize = 64;
 
 /// At most 16 messages per client.
+#[allow(dead_code)]
 pub(crate) const BUFFER_OUT_MAX: usize = 16;
 
 /// Expected version from the lektord daemin.
diff --git a/src/rust/amalib/src/cache.rs b/src/rust/amalib/src/db_cache.rs
similarity index 96%
rename from src/rust/amalib/src/cache.rs
rename to src/rust/amalib/src/db_cache.rs
index f86ec433b2085dadf66edad0ddaa35b867372cd0..776c713baca4c74bcbb327c57a50d3cf530f54ff 100644
--- a/src/rust/amalib/src/cache.rs
+++ b/src/rust/amalib/src/db_cache.rs
@@ -1,9 +1,8 @@
+//! Datastructure used to cache requests to the lektord server. Also permit to
+//! patch returned data with local data.
+
 use crate::*;
-use hashbrown::HashMap;
 use lru::LruCache;
-use serde::{Deserialize, Serialize};
-use smallstring::SmallString;
-use std::num::NonZeroUsize;
 
 /// We cache objects locally so we don't have to always make requests to
 /// lektord. When we get the update stored database event from mpv we clear the
diff --git a/src/rust/amalib/src/db_objects.rs b/src/rust/amalib/src/db_objects.rs
index 2667a2567637b3b6a2f96e20796c18ef56f95af6..5ec8f058c548762375d5f543e4514dc4f948cc59 100644
--- a/src/rust/amalib/src/db_objects.rs
+++ b/src/rust/amalib/src/db_objects.rs
@@ -1,7 +1,7 @@
-use crate::tags::*;
-use getset::{CopyGetters, Getters};
-use serde::{Deserialize, Serialize};
-use smallstring::SmallString;
+//! Collection of structures to describe objects retrieved from the lektord
+//! server.
+
+use crate::*;
 
 /// Represent a global id for a kara, the pair (database, id).
 #[derive(Debug, Deserialize, Serialize, Getters, CopyGetters)]
@@ -60,55 +60,83 @@ impl From<(SmallString, i64)> for KaraId {
 }
 
 impl<'a> TaggedObject<'a> for Playlist {
-    type TagKeyType = <SimpleTagSystem as TaggedObject<'a>>::TagKeyType;
-    type TagValueType = <SimpleTagSystem as TaggedObject<'a>>::TagValueType;
-    type TagKeyIterator = <SimpleTagSystem as TaggedObject<'a>>::TagKeyIterator;
-    type TagValueIterator = <SimpleTagSystem as TaggedObject<'a>>::TagValueIterator;
+    type TK = <SimpleTagSystem as TaggedObject<'a>>::TK;
+    type TV = <SimpleTagSystem as TaggedObject<'a>>::TV;
+    type TKIter = <SimpleTagSystem as TaggedObject<'a>>::TKIter;
+    type TVIter = <SimpleTagSystem as TaggedObject<'a>>::TVIter;
 
-    fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) {
+    fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool {
         self.tags.add_tag(tag, value)
     }
 
-    fn delete_tag<S: AsRef<str>>(&mut self, tag: S) {
-        self.tags.delete_tag(tag)
+    fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool {
+        self.tags.remove_tag(tag, value)
+    }
+
+    fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        self.tags.drop_tag(tag)
+    }
+
+    fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        self.tags.empty_tag(tag)
     }
 
-    fn clear_tags(&mut self) {
-        self.tags.clear()
+    fn clear_tags(&mut self) -> bool {
+        self.tags.clear_tags()
     }
 
-    fn tags(&'a self) -> Self::TagKeyIterator {
+    fn tags<'b>(&'b self) -> Self::TKIter
+    where
+        'b: 'a,
+    {
         self.tags.tags()
     }
 
-    fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> {
+    fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter>
+    where
+        'b: 'a,
+    {
         self.tags.iter_tag(tag)
     }
 }
 
 impl<'a> TaggedObject<'a> for KaraInfo {
-    type TagKeyType = <SimpleTagSystem as TaggedObject<'a>>::TagKeyType;
-    type TagValueType = <SimpleTagSystem as TaggedObject<'a>>::TagValueType;
-    type TagKeyIterator = <SimpleTagSystem as TaggedObject<'a>>::TagKeyIterator;
-    type TagValueIterator = <SimpleTagSystem as TaggedObject<'a>>::TagValueIterator;
+    type TK = <SimpleTagSystem as TaggedObject<'a>>::TK;
+    type TV = <SimpleTagSystem as TaggedObject<'a>>::TV;
+    type TKIter = <SimpleTagSystem as TaggedObject<'a>>::TKIter;
+    type TVIter = <SimpleTagSystem as TaggedObject<'a>>::TVIter;
 
-    fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>) {
+    fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool {
         self.tags.add_tag(tag, value)
     }
 
-    fn delete_tag<S: AsRef<str>>(&mut self, tag: S) {
-        self.tags.delete_tag(tag)
+    fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool {
+        self.tags.remove_tag(tag, value)
+    }
+
+    fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        self.tags.drop_tag(tag)
+    }
+
+    fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        self.tags.empty_tag(tag)
     }
 
-    fn clear_tags(&mut self) {
-        self.tags.clear()
+    fn clear_tags(&mut self) -> bool {
+        self.tags.clear_tags()
     }
 
-    fn tags(&'a self) -> Self::TagKeyIterator {
+    fn tags<'b>(&'b self) -> Self::TKIter
+    where
+        'b: 'a,
+    {
         self.tags.tags()
     }
 
-    fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> {
+    fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter>
+    where
+        'b: 'a,
+    {
         self.tags.iter_tag(tag)
     }
 }
diff --git a/src/rust/amalib/src/db_tags.rs b/src/rust/amalib/src/db_tags.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0433d4c9e7c79c8743b64acf06279d39a4275602
--- /dev/null
+++ b/src/rust/amalib/src/db_tags.rs
@@ -0,0 +1,186 @@
+//! Represent tags on playlists and karas in the database.
+
+use crate::*;
+
+/// Represent an object that can be tagged, e.g. a playlist or a kara. We
+/// describe all the operations on such object here.
+pub trait TaggedObject<'a> {
+    /// The type for the keys, e.g. the tag names.
+    type TK: AsRef<str> + Clone + 'a;
+
+    /// The type for the values of the tags. We enforce a string representation
+    /// for such tag.
+    type TV: AsRef<str> + Clone + 'a;
+
+    /// The type of iterator for the list of keys.
+    type TKIter: IntoIterator<Item = &'a Self::TK>;
+
+    /// The type of iterator for the list the values of a tag.
+    type TVIter: IntoIterator<Item = &'a Self::TV>;
+
+    /// Add a tag to the object. Returns true if the action did something.
+    fn add_tag(&mut self, tag: Self::TK, value: Option<Self::TV>) -> bool;
+
+    /// Delete a value for a tag. Returns true if the action did something.
+    fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool;
+
+    /// Delete all values for a tag and the tag itself for the object. Returns
+    /// true if the action did something.
+    fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool;
+
+    /// Delete all values for a tag for the object. If the tag did not exist,
+    /// create it. Returns true if the action did something.
+    fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool;
+
+    /// Remove all tags from the object. Returns true if the action did
+    /// something.
+    fn clear_tags(&mut self) -> bool;
+
+    /// Get all the tags present on a kara.
+    fn tags<'b>(&'b self) -> Self::TKIter
+    where
+        'b: 'a;
+
+    /// Iterate over the values of a tag.
+    fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter>
+    where
+        'b: 'a;
+
+    /// Has a tag with the specified name.
+    fn has_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> bool
+    where
+        'b: 'a,
+    {
+        self.tags().into_iter().any(|t| t.as_ref() == tag.as_ref())
+    }
+
+    /// Contains the (tag, value) pair.
+    fn has_tag_value<'b, S: AsRef<str>>(&'b self, tag: S, value: S) -> bool
+    where
+        'b: 'a,
+    {
+        self.iter_tag(tag)
+            .map(|values| values.into_iter().any(|v| v.as_ref() == value.as_ref()))
+            .unwrap_or_default()
+    }
+
+    /// Patch the tagged object with a list of actions to do in the passed
+    /// order. If the object was patched, return true, otherwise return false.
+    fn patch<'b, I>(&'b mut self, overrides: I) -> bool
+    where
+        I: IntoIterator<Item = TagOverride<Self::TK, Self::TV>>,
+        'a: 'b,
+    {
+        use TagOverride::*;
+        overrides
+            .into_iter()
+            .map(|patch| match patch {
+                ClearTags => self.clear_tags(),
+                DeleteTag(tag) => self.drop_tag(tag),
+                AddTagValue(tag, value) => self.add_tag(tag, Some(value)),
+                DeleteTagValue(tag, value) => self.remove_tag(tag, value),
+                EmptyTag(tag) => self.empty_tag(tag),
+            })
+            .count()
+            != 0
+    }
+}
+
+/// A simple tag system. Any object can reuse this type or implement the
+/// [TaggedObject] trait itself. To reuse this type, you can forward calls to
+/// the simple tag field when implementing the trait.
+pub type SimpleTagSystem = HashMap<SmallString, HashSet<SmallString>>;
+
+impl<'a> TaggedObject<'a> for SimpleTagSystem {
+    type TK = SmallString;
+    type TV = SmallString;
+    type TKIter = hashbrown::hash_map::Keys<'a, SmallString, HashSet<SmallString>>;
+    type TVIter = hashbrown::hash_set::Iter<'a, SmallString>;
+
+    fn add_tag(&mut self, tag: SmallString, value: Option<SmallString>) -> bool {
+        match self.get_mut(&tag) {
+            Some(values) => {
+                let old_count = values.len();
+                values.extend(value);
+                old_count != values.len()
+            }
+            None => {
+                self.insert(tag, value.into_iter().collect());
+                true
+            }
+        }
+    }
+
+    fn remove_tag<S1: AsRef<str>, S2: AsRef<str>>(&mut self, tag: S1, value: S2) -> bool {
+        self.get_mut(tag.as_ref())
+            .map(|values| values.remove(value.as_ref()))
+            .unwrap_or_default()
+    }
+
+    fn drop_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        either!(!self.has_tag(tag.as_ref()) => false; {
+            self.remove(tag.as_ref());
+            true
+        })
+    }
+
+    fn empty_tag<S: AsRef<str>>(&mut self, tag: S) -> bool {
+        self.get_mut(tag.as_ref())
+            .map(|values| {
+                let old_count = values.len();
+                values.clear();
+                old_count != values.len()
+            })
+            .or_else(|| Some(self.add_tag(SmallString::from(tag.as_ref()), None)))
+            .unwrap_or_default()
+    }
+
+    fn clear_tags(&mut self) -> bool {
+        either!(self.tags().count() == 0 => false; {
+            self.clear();
+            true
+        })
+    }
+
+    fn tags<'b>(&'b self) -> Self::TKIter
+    where
+        'b: 'a,
+    {
+        self.keys()
+    }
+
+    fn iter_tag<'b, S: AsRef<str>>(&'b self, tag: S) -> Option<Self::TVIter>
+    where
+        'b: 'a,
+    {
+        self.get(tag.as_ref()).map(|tags| tags.iter())
+    }
+
+    fn has_tag<'b, S: AsRef<str>>(&self, tag: S) -> bool
+    where
+        'b: 'a,
+    {
+        self.contains_key(tag.as_ref())
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Represents a patch action to do on a tagged object.
+pub enum TagOverride<TK: AsRef<str> + Clone, TV: AsRef<str>> {
+    /// Remove all tags.
+    ClearTags,
+
+    /// Remove a tag and all it's values.
+    DeleteTag(TK),
+
+    /// Clear all the values for a tag, or if the tag doesn't exist, create one
+    /// with no associated values for the object.
+    EmptyTag(TK),
+
+    /// Add a value to the tag's value list. If the tag is not already present,
+    /// create the tag.
+    AddTagValue(TK, TV),
+
+    /// Remove a value from a tag's value list.
+    DeleteTagValue(TK, TV),
+}
diff --git a/src/rust/amalib/src/lib.rs b/src/rust/amalib/src/lib.rs
index 87ac4a9b5835fecd697099086350ff5368a4142e..d02b0fd32284012971492952d47efc4750f5cebb 100644
--- a/src/rust/amalib/src/lib.rs
+++ b/src/rust/amalib/src/lib.rs
@@ -4,25 +4,29 @@
 //! communicate with the lektord server and elements to store and organise the
 //! queried informations.
 
-mod cache;
 mod connexion;
 mod constants;
+mod db_cache;
 mod db_objects;
+mod db_tags;
 mod query;
 mod response;
-mod tags;
 mod uri;
 
-pub use cache::*;
 pub use connexion::*;
+pub use db_cache::*;
 pub use db_objects::*;
+pub use db_tags::*;
 pub use query::*;
 pub use response::*;
-pub use tags::*;
 pub use uri::*;
 
 pub(crate) use commons::{log::*, *};
-pub(crate) use std::str::FromStr;
+pub(crate) use getset::{CopyGetters, Getters};
+pub(crate) use hashbrown::{HashMap, HashSet};
+pub(crate) use serde::{Deserialize, Serialize};
+pub(crate) use smallstring::SmallString;
+pub(crate) use std::{num::NonZeroUsize, ops::Range, str::FromStr, string::ToString};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum LektorPriorityLevel {
diff --git a/src/rust/amalib/src/query.rs b/src/rust/amalib/src/query.rs
index ad0bc165e71e1059c6d267f17ad60d028186da84..07dc88576692399b3ced6d163a386e1abc336d3f 100644
--- a/src/rust/amalib/src/query.rs
+++ b/src/rust/amalib/src/query.rs
@@ -1,7 +1,6 @@
 //! Contains files to create and build queries to send latter to lektord.
 
 use crate::*;
-use std::{ops::Range, string::ToString};
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub(crate) enum LektorQueryLineType {
diff --git a/src/rust/amalib/src/tags.rs b/src/rust/amalib/src/tags.rs
deleted file mode 100644
index 0d003575e25c1ad19aa087bba1e06f086255496b..0000000000000000000000000000000000000000
--- a/src/rust/amalib/src/tags.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use hashbrown::HashMap;
-use smallstring::SmallString;
-
-/// Represent an object that can be tagged, e.g. a playlist or a kara. We
-/// describe all the operations on such object here.
-pub trait TaggedObject<'a> {
-    /// The type for the keys, e.g. the tag names.
-    type TagKeyType: AsRef<str> + 'a;
-
-    /// The type for the values of the tags. We enforce a string representation
-    /// for such tag.
-    type TagValueType: AsRef<str> + 'a;
-
-    /// The type of iterator for the list of keys.
-    type TagKeyIterator: Iterator<Item = &'a Self::TagKeyType>;
-
-    /// The type of iterator for the list the values of a tag.
-    type TagValueIterator: Iterator<Item = &'a Self::TagValueType>;
-
-    /// Add a tag to the object.
-    fn add_tag(&mut self, tag: Self::TagKeyType, value: Option<Self::TagValueType>);
-
-    /// Delete all values for a tag for the object.
-    fn delete_tag<S: AsRef<str>>(&mut self, tag: S);
-
-    /// Remove all tags from the object.
-    fn clear_tags(&mut self);
-
-    /// Get all the tags present on a kara.
-    fn tags(&'a self) -> Self::TagKeyIterator;
-
-    /// Iterate over the values of a tag.
-    fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator>;
-
-    /// Has a tag with the specified name.
-    fn has_tag<S: AsRef<str>>(&'a self, tag: S) -> bool {
-        self.tags().any(|t| t.as_ref() == tag.as_ref())
-    }
-
-    /// Contains the (tag, value) pair.
-    fn has_tag_value<S: AsRef<str>>(&'a self, tag: S, value: S) -> bool {
-        self.iter_tag(tag)
-            .map(|mut values| values.any(|v| v.as_ref() == value.as_ref()))
-            .unwrap_or_default()
-    }
-}
-
-/// A simple tag system. Any object can reuse this type or implement the
-/// [TaggedObject] trait itself. To reuse this type, you can forward calls to
-/// the simple tag field when implementing the trait.
-pub(crate) type SimpleTagSystem = HashMap<SmallString, Vec<SmallString>>;
-
-impl<'a> TaggedObject<'a> for SimpleTagSystem {
-    type TagKeyType = SmallString;
-    type TagValueType = SmallString;
-    type TagKeyIterator = hashbrown::hash_map::Keys<'a, SmallString, Vec<SmallString>>;
-    type TagValueIterator = std::slice::Iter<'a, SmallString>;
-
-    fn tags(&'a self) -> Self::TagKeyIterator {
-        self.keys()
-    }
-
-    fn iter_tag<S: AsRef<str>>(&'a self, tag: S) -> Option<Self::TagValueIterator> {
-        self.get(tag.as_ref()).map(|tags| tags.iter())
-    }
-
-    fn add_tag(&mut self, tag: SmallString, value: Option<SmallString>) {
-        match self.get_mut(&tag) {
-            Some(values) => values.extend(value),
-            None => {
-                self.insert(tag, value.into_iter().collect());
-            }
-        }
-    }
-
-    fn delete_tag<S: AsRef<str>>(&mut self, tag: S) {
-        if let Some(values) = self.get_mut(tag.as_ref()) {
-            values.clear()
-        }
-    }
-
-    fn clear_tags(&mut self) {
-        self.clear()
-    }
-
-    fn has_tag<S: AsRef<str>>(&'a self, tag: S) -> bool {
-        self.contains_key(tag.as_ref())
-    }
-}