diff --git a/declearn/dataset/__init__.py b/declearn/dataset/__init__.py
index 4c46d218cd0e5af76610fb0a256b25ac55955427..56dfdb2e00869e1e1db8e4c0bdb40c2f9906595c 100644
--- a/declearn/dataset/__init__.py
+++ b/declearn/dataset/__init__.py
@@ -30,6 +30,9 @@ API tools
 * [DataSpec][declearn.dataset.DataSpecs]:
     Dataclass to wrap a dataset's metadata.
 * [load_dataset_from_json][declearn.dataset.load_dataset_from_json"]
+    Utility function to parse a JSON into a dataset object
+* [split_data][declearn.dataset.split_data]
+    Utility to split a single dataset into shards
 
 Dataset subclasses
 ------------------
@@ -48,3 +51,4 @@ from . import utils
 from . import examples
 from ._base import Dataset, DataSpecs, load_dataset_from_json
 from ._inmemory import InMemoryDataset
+from .split_data import split_data
diff --git a/declearn/quickrun/_split_data.py b/declearn/dataset/split_data.py
similarity index 66%
rename from declearn/quickrun/_split_data.py
rename to declearn/dataset/split_data.py
index 4e595f424ab7a3d5c33b6df0142042c25cb225ae..b6094f342048703190e1a7459ecf05289018ca68 100644
--- a/declearn/quickrun/_split_data.py
+++ b/declearn/dataset/split_data.py
@@ -35,11 +35,11 @@ instance sparse data
 import os
 from typing import Optional, Tuple, Union
 
+import fire
 import numpy as np
 
 from declearn.dataset.examples import load_mnist
 from declearn.dataset.utils import load_data_array, split_multi_classif_dataset
-from declearn.quickrun._config import DataSplitConfig
 
 
 def load_data(
@@ -91,8 +91,17 @@ def load_data(
     return inputs, labels
 
 
-def split_data(data_config: DataSplitConfig, folder: str) -> None:
-    """Download and randomly split a dataset into shards.
+def split_data(
+    folder: str = ".",
+    data_file: Optional[str] = None,
+    label_file: Optional[Union[str, int]] = None,
+    n_shards: int = 3,
+    scheme: str = "iid",
+    perc_train: float = 0.8,
+    seed: Optional[int] = None,
+) -> None:
+
+    """Randomly split a dataset into shards.
 
     The resulting folder structure is :
         folder/
@@ -107,29 +116,49 @@ def split_data(data_config: DataSplitConfig, folder: str) -> None:
 
     Parameters
     ----------
-    data_config: DataSplitConfig
-        A DataSplitConfig instance, see class documentation for details
+    folder: str, default = "."
+        Path to the folder where to add a data folder
+        holding output shard-wise files
+    data_file: str or None, default=None
+        Optional path to a folder where to find the data.
+        If None, default to the MNIST example.
+    target_file: str or int or None, default=None
+        If str, path to the labels file to import. If int, column of
+        the data file to be used as labels. Required if data is not None,
+        ignored if data is None.
+    n_shards: int
+        Number of shards between which to split the data.
+    scheme: {"iid", "labels", "biased"}, default="iid"
+        Splitting scheme(s) to use. In all cases, shards contain mutually-
+        exclusive samples and cover the full raw training data.
+        - If "iid", split the dataset through iid random sampling.
+        - If "labels", split into shards that hold all samples associated
+        with mutually-exclusive target classes.
+        - If "biased", split the dataset through random sampling according
+        to a shard-specific random labels distribution.
+    perc_train:  float, default= 0.8
+        Train/validation split in each client dataset, must be in the
+        ]0,1] range.
+    seed: int or None, default=None
+        Optional seed to the RNG used for all sampling operations.
     """
     # Select output folder.
-    if data_config.data_folder:
-        folder = os.path.dirname(data_config.data_folder)
-    else:
-        folder = f"data_{data_config.scheme}"
+    folder = os.path.join(folder, f"data_{scheme}")
     # Value-check the 'perc_train' parameter.
-    if not 0.0 < data_config.perc_train <= 1.0:
+    if not 0.0 < perc_train <= 1.0:
         raise ValueError("'perc_train' should be a float in ]0,1]")
     # Load the dataset and split it.
-    inputs, labels = load_data(data_config.data_file, data_config.label_file)
+    inputs, labels = load_data(data_file, label_file)
     print(
-        f"Splitting data into {data_config.n_shards} shards "
-        f"using the '{data_config.scheme}' scheme."
+        f"Splitting data into {n_shards} shards "
+        f"using the '{scheme}' scheme."
     )
     split = split_multi_classif_dataset(
         dataset=(inputs, labels),
-        n_shards=data_config.n_shards,
-        scheme=data_config.scheme,  # type: ignore
-        p_valid=(1 - data_config.perc_train),
-        seed=data_config.seed,
+        n_shards=n_shards,
+        scheme=scheme,  # type: ignore
+        p_valid=(1 - perc_train),
+        seed=seed,
     )
     # Export the resulting shard-wise data to files.
     for idx, ((x_train, y_train), (x_valid, y_valid)) in enumerate(split):
@@ -140,3 +169,12 @@ def split_data(data_config: DataSplitConfig, folder: str) -> None:
         if len(x_valid):
             np.save(os.path.join(subdir, "valid_data.npy"), x_valid)
             np.save(os.path.join(subdir, "valid_target.npy"), y_valid)
+
+
+def main():
+    "fire-wrapped split data"
+    fire.Fire(split_data)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/declearn/dataset/utils/__init__.py b/declearn/dataset/utils/__init__.py
index c19f9cb1efe452c6763bff5c3286ca5e9f442dc0..1516e660f8c896b2b81942f45ea92ee5d36d43b3 100644
--- a/declearn/dataset/utils/__init__.py
+++ b/declearn/dataset/utils/__init__.py
@@ -37,6 +37,6 @@ Data splitting
 [declearn.dataset.utils.split_multi_classif_dataset]:
     Split a classification dataset into (opt. heterogeneous) shards.
 """
-from ._sparse import sparse_from_file, sparse_to_file
 from ._save_load import load_data_array, save_data_array
+from ._sparse import sparse_from_file, sparse_to_file
 from ._split_classif import split_multi_classif_dataset
diff --git a/declearn/quickrun/_config.py b/declearn/quickrun/_config.py
index 7614ed316dc6ded4e4de9dedce8121c4e1071a1a..ad32be23e20cbd96cd80c3cf7e9fd035111bbf56 100644
--- a/declearn/quickrun/_config.py
+++ b/declearn/quickrun/_config.py
@@ -25,7 +25,7 @@ from declearn.utils import TomlConfig
 
 __all__ = [
     "ModelConfig",
-    "DataSplitConfig",
+    "DataSourceConfig",
     "ExperimentConfig",
 ]
 
@@ -40,36 +40,12 @@ class ModelConfig(TomlConfig):
 
 
 @dataclasses.dataclass
-class DataSplitConfig(TomlConfig):
+class DataSourceConfig(TomlConfig):
     """Dataclass associated with the functions
-    declearn.quickrun._split_data:split_data and
     declearn.quickrun._parser:parse_data_folder
 
     data_folder: str
-        Absolute path to the folder where to export shard-wise files,
-        and/or to the main folder hosting the data.
-    n_shards: int
-        Number of shards between which to split the data.
-    data_file: str or None, default=None
-        Optional path to a folder where to find the data.
-        If None, default to the MNIST example.
-    target_file: str or int or None, default=None
-        If str, path to the labels file to import. If int, column of
-        the data file to be used as labels. Required if data is not None,
-        ignored if data is None.
-    scheme: {"iid", "labels", "biased"}, default="iid"
-        Splitting scheme(s) to use. In all cases, shards contain mutually-
-        exclusive samples and cover the full raw training data.
-        - If "iid", split the dataset through iid random sampling.
-        - If "labels", split into shards that hold all samples associated
-        with mutually-exclusive target classes.
-        - If "biased", split the dataset through random sampling according
-        to a shard-specific random labels distribution.
-    perc_train:  float, default= 0.8
-        Train/validation split in each client dataset, must be in the
-        ]0,1] range.
-    seed: int or None, default=None
-        Optional seed to the RNG used for all sampling operations.
+        Absolute path to the to the main folder hosting the data.
     client_names: list or None
         List of custom client names to look for in the data_folder.
         If None, default to expected prefix search.
@@ -79,16 +55,7 @@ class DataSplitConfig(TomlConfig):
         If None, , default to expected prefix search.
     """
 
-    # Common args
     data_folder: Optional[str] = None
-    # split_data args
-    n_shards: int = 3
-    data_file: Optional[str] = None
-    label_file: Optional[Union[str, int]] = None
-    scheme: str = "iid"
-    perc_train: float = 0.8
-    seed: Optional[int] = None
-    # parse_data_folder args
     client_names: Optional[List[str]] = None
     dataset_names: Optional[Dict[str, str]] = None
 
diff --git a/declearn/quickrun/_parser.py b/declearn/quickrun/_parser.py
index a753d7dcfd26f5379dcb883af8947f13355d5ea3..b1b67b6741505a7886d8f355a2991cdf46a3d7fe 100644
--- a/declearn/quickrun/_parser.py
+++ b/declearn/quickrun/_parser.py
@@ -16,24 +16,19 @@
 # limitations under the License.
 
 """
-#TODO
+Utils parsing a data folder following a standard format into a nested
+dictionnary
 """
 
 import os
 from pathlib import Path
-from typing import Dict, List, Optional
+from typing import Dict, Optional
 
-from declearn.test_utils import make_importable
-
-# Perform local imports.
-# pylint: disable=wrong-import-order, wrong-import-position
-with make_importable(os.path.dirname(__file__)):
-    from _config import ExperimentConfig
-# pylint: enable=wrong-import-order, wrong-import-position
+from declearn.quickrun._config import DataSourceConfig
 
 
 def parse_data_folder(
-    expe_config: ExperimentConfig,
+    data_config: DataSourceConfig,
     folder: Optional[str] = None,
 ) -> Dict:
     """Utils parsing a data folder following a standard format into a nested
@@ -53,17 +48,16 @@ def parse_data_folder(
 
     Parameters:
     -----------
-    expe_config : ExperimentConfig
-        ExperimentConfig instance, see class documentation for details.
+    data_config : DataSourceConfig
+        DataSourceConfig instance, see class documentation for details.
     folder : str or None
         The main experiment folder in which to look for a `data*` folder.
         Overwritten by data_folder.
     """
 
-    data_folder = expe_config.data_folder
-    client_names = expe_config.client_names
-    dataset_names = expe_config.dataset_names
-    scheme = expe_config.scheme
+    data_folder = data_config.data_folder
+    client_names = data_config.client_names
+    dataset_names = data_config.dataset_names
 
     if not folder and not data_folder:
         raise ValueError(
@@ -71,13 +65,13 @@ def parse_data_folder(
         )
     # Data_folder
     if not data_folder:
-        search_str = f"_{scheme}" if scheme else "*"
-        gen_folders = Path(folder).glob(f"data{search_str}")  # type: ignore
+        gen_folders = Path(folder).glob("data*")  # type: ignore
         data_folder = next(gen_folders, False)  # type: ignore
         if not data_folder:
             raise ValueError(
                 f"No folder starting with 'data' found in {folder}. "
-                "Please store your data under a 'data_*' folder"
+                "Please store your split data under a 'data_*' folder. "
+                "To use an example dataset run `declearn-split` first."
             )
         if next(gen_folders, False):
             raise ValueError(
@@ -86,7 +80,13 @@ def parse_data_folder(
                 "parent folder"
             )
     else:
-        data_folder = Path(data_folder)
+        if os.path.isdir(data_folder):
+            data_folder = Path(data_folder)
+        else:
+            raise ValueError(
+                f"{data_folder} is not a valid path. To use an example "
+                "dataset run `declearn-split` first."
+            )
     # Get clients dir
     if client_names:
         if isinstance(client_names, list):
diff --git a/declearn/quickrun/run.py b/declearn/quickrun/run.py
index 36db434b2d4f08ff2c797025bbbcd2746d08fe27..71441b4946c9eb7ce58faf1374b15e1975fa10ba 100644
--- a/declearn/quickrun/run.py
+++ b/declearn/quickrun/run.py
@@ -40,18 +40,19 @@ import textwrap
 from datetime import datetime
 from typing import Dict, List, Optional, Tuple
 
+import fire
+
 from declearn.communication import NetworkClientConfig, NetworkServerConfig
 from declearn.dataset import InMemoryDataset
 from declearn.main import FederatedClient, FederatedServer
 from declearn.main.config import FLOptimConfig, FLRunConfig
 from declearn.model.api import Model
 from declearn.quickrun._config import (
-    DataSplitConfig,
+    DataSourceConfig,
     ExperimentConfig,
     ModelConfig,
 )
 from declearn.quickrun._parser import parse_data_folder
-from declearn.quickrun._split_data import split_data
 from declearn.test_utils import make_importable
 from declearn.utils import (
     LOGGING_LEVEL_MAJOR,
@@ -63,12 +64,6 @@ from declearn.utils import (
 __all__ = ["quickrun"]
 
 
-DEFAULT_FOLDER = os.path.join(
-    os.path.dirname(__file__),
-    "../../examples/quickrun",
-)
-
-
 def get_model(folder, model_config) -> Model:
     "Return a model instance from a model config instance"
     path = model_config.model_file or os.path.join(folder, "model.py")
@@ -82,6 +77,7 @@ def get_model(folder, model_config) -> Model:
 
 
 def get_checkpoint(folder: str, expe_config: ExperimentConfig) -> str:
+    """Return the checkpoint folder, either default or as given in config"""
     if expe_config.checkpoint:
         checkpoint = expe_config.checkpoint
     else:
@@ -146,15 +142,13 @@ def run_client(
     client.run()
 
 
-def get_toml_folder(config: Optional[str] = None) -> Tuple[str, str]:
-    """Deternmine if provided config is a file or a directory, and
+def get_toml_folder(config: str) -> Tuple[str, str]:
+    """Determine if provided config is a file or a directory, and
     return :
     * The path to the TOML config file
     * The path to the main folder of the experiment
     """
     # default to the mnist example
-    if not config:
-        config = DEFAULT_FOLDER
     config = os.path.abspath(config)
     # check if config is TOML or dir
     if os.path.isfile(config):
@@ -170,16 +164,11 @@ def get_toml_folder(config: Optional[str] = None) -> Tuple[str, str]:
     return toml, folder
 
 
-def locate_or_create_split_data(toml: str, folder: str) -> Dict:
+def locate_split_data(toml: str, folder: str) -> Dict:
     """Attempts to find split data according to the config toml or
-    or the default behavior. If failed, attempts to find full data
-    according to the config toml and split it"""
-    data_config = DataSplitConfig.from_toml(toml, False, "data")
-    try:
-        client_dict = parse_data_folder(data_config, folder)
-    except ValueError:
-        split_data(data_config, folder)
-        client_dict = parse_data_folder(data_config, folder)
+    or the default behavior"""
+    data_config = DataSourceConfig.from_toml(toml, False, "data")
+    client_dict = parse_data_folder(data_config, folder)
     return client_dict
 
 
@@ -194,22 +183,36 @@ def server_to_client_network(
     )
 
 
-def quickrun(config: Optional[str] = None) -> None:
-    """Run a server and its clients using multiprocessing.
+def quickrun(config: str) -> None:
+    """
+    Run a server and its clients using multiprocessing.
+
+    The script requires to be provided with the path a TOML file
+    with all the elements required to configurate an FL experiment,
+    or the path to a folder containing :
+
+    * a TOML file with all the elements required to configurate an
+    FL experiment
+    * A declearn model
+    * A data folder, structured in a specific way
+
+    Parameters:
+    ----
+    config: str
+        Path to either a toml file or a properly formatted folder
+        containing the elements required to launch an FL experiment
 
-    The kwargs are the arguments expected by split_data,
-    see [the documentation][declearn.quickrun._split_data]
     """
     toml, folder = get_toml_folder(config)
     # locate split data or split it if needed
-    client_dict = locate_or_create_split_data(toml, folder)
+    client_dict = locate_split_data(toml, folder)
     # Parse toml files
     ntk_server_cfg = NetworkServerConfig.from_toml(toml, False, "network")
     ntk_client_cfg = server_to_client_network(ntk_server_cfg)
     optim_cgf = FLOptimConfig.from_toml(toml, False, "optim")
     run_cfg = FLRunConfig.from_toml(toml, False, "run")
-    model_cfg = ModelConfig.from_toml(toml, False, "model")
-    expe_cfg = ExperimentConfig.from_toml(toml, False, "experiment")
+    model_cfg = ModelConfig.from_toml(toml, False, "model", True)
+    expe_cfg = ExperimentConfig.from_toml(toml, False, "experiment", True)
     # Set up a (func, args) tuple specifying the server process.
     p_server = (
         run_server,
@@ -230,39 +233,9 @@ def quickrun(config: Optional[str] = None) -> None:
     )
 
 
-def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
-    """Set up and run a command-line arguments parser."""
-    usage = """
-        Quickly run an example locally using declearn.
-        The script requires to be provided with the path a TOML file
-        with all the elements required to configurate an FL experiment,
-        or the path to a folder containing :
-        * a TOML file with all the elements required to configurate an
-        FL experiment
-        * A declearn model
-        * A data folder, structured in a specific way
-
-        If not provided with this, the script defaults to the MNIST example
-        provided by declearn in `declearn.example.quickrun`.
-    """
-    usage = re.sub("\n *(?=[a-z])", " ", textwrap.dedent(usage))
-    parser = argparse.ArgumentParser(
-        formatter_class=argparse.RawTextHelpFormatter,
-        usage=re.sub("- ", "-", usage),
-    )
-    parser.add_argument(
-        "--config",
-        default=None,
-        dest="config",
-        help="Path to the root folder where to export data.",
-    )
-    return parser.parse_args(args)
-
-
-def main(args: Optional[List[str]] = None) -> None:
-    """Quikcrun based on commandline-input arguments."""
-    cmdargs = parse_args(args)
-    quickrun(config=cmdargs.config)
+def main() -> None:
+    """Fire-wrapped quickrun"""
+    fire.Fire(quickrun)
 
 
 if __name__ == "__main__":
diff --git a/declearn/utils/_toml_config.py b/declearn/utils/_toml_config.py
index 249db82556941d6264afe6701ac47ee93f052b2e..476d960e9da8ed3dc23510cc7a11dad082176b22 100644
--- a/declearn/utils/_toml_config.py
+++ b/declearn/utils/_toml_config.py
@@ -298,6 +298,7 @@ class TomlConfig:
         path: str,
         warn_user: bool = True,
         use_section: Optional[str] = None,
+        section_fail_ok: bool = False,
     ) -> Self:
         """Parse a structured configuration from a TOML file.
 
@@ -325,6 +326,9 @@ class TomlConfig:
             If not None, points to a specific section of the TOML that
             should be used, rather than the whole file. Useful to parse
             orchestrating TOML files, e.g. quickrun.
+        section_fail_ok: bool, default=False
+            If True, allow the section specified in use_section to be
+            missing from the TOML file without raising an Error.
 
         Raises
         ------
@@ -352,7 +356,8 @@ class TomlConfig:
             try:
                 config = config[use_section]
             except KeyError as exc:
-                raise KeyError("Specified section not found") from exc
+                if not section_fail_ok:
+                    raise KeyError("Specified section not found") from exc
         params = {}  # type: Dict[str, Any]
         for field in dataclasses.fields(cls):
             # Case when the section is provided: set it up for parsing.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 72b041025376b0a29cc152db0209fee50ca9e2c2..a8d5c355a643214e833e93abffa5ddb75921ca7a 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -1,38 +1,91 @@
 # Quickstart
 
-This section provides with demonstration code on how to run a simple federated
-learning task using declearn, that requires minimal adjustments to be run for
-real (mainly, to provide with a valid network configuration and actual data).
+**Here's where to start if you want to quickly understand what `declearn` does**. This tutorial exepects a basic understanding of [federated learning](https://en.wikipedia.org/wiki/Federated_learning).
 
-You may find even more concrete examples on our gitlab repository
-[here](https://gitlab.inria.fr/magnet/declearn/declearn2/examples).
-The Heart UCI example may notably be run as-is, either locally or on a
-real-life network with minimal command-line parametrization.
+We show different ways to use `declearn` on a well-known example, the
+[MNIST dataset](http://yann.lecun.com/exdb/mnist/) (see [section 1](#1-federated-learning-on-the-mnist-dataset)). We then look at how to
+use declearn on your own problem (see [section 2](#2-federated-learning-on-your-own-dataset)).
 
-## Setting
+## 1. Federated learning on the MNIST dataset
 
-Here is a quickstart example on how to set up a federated learning process
-to learn a LASSO logistic regression model (using a scikit-learn backend)
-using pre-processed data, formatted as csv files with a "label" column,
+**We are going to train a common model between three simulated clients on the
+classic [MNIST dataset](http://yann.lecun.com/exdb/mnist/)**. The input of the
+model is a set of images of handwritten digits, and the model needs to
+determine which number between $0$ and $9$ each image corresponds to.
+We show two ways to use `declearn` on this problem.
+
+### 1.1. Quickrun mode
+
+**The quickrun mode is the simplest way to simulate a federated learning
+process on a single machine with `declearn`**. It does not require to
+understand the details of `declearn` implementation. It requires a basic
+understanding of federated learning.
+
+---
+**To test this on the MNIST example**, you can follow along
+[this colab notebook](https://colab.research.google.com/drive/13sBDOQeorI6dfziSoyRpU4q4iGuESIPo?usp=sharing).
+
+---
+
+**If you want to run this locally**, the detailed notebook can be boiled down
+to five `bash` commands. Set up a dedicated `conda` of `venv` environment,
+and run :
+
+```bash
+git clone https://gitlab.inria.fr/magnet/declearn/declearn2 &&
+cd declearn2 &&
+pip install .[tensorflow,websockets] &&
+declearn-split --folder "examples/mnist_quickrun" &&
+declearn-quickrun --config "examples/mnist_quickrun/config.toml"
+```
+
+**To better understand the details** of how what happens under the hood you
+can look at what the key element of the declearn process are in
+[section 1.2.](#12-python-script). To understand how to use the quickrun mode
+in practice see [section 2.1.](#21-quickrun-on-your-problem).
+
+### 1.2. Python script
+
+#### MNIST
+
+**The quickrun mode abstracts away a lot of important elements** of the
+process, and is only designed to simulate an FL experiment: the clients all
+run on the same machine. In real life deployment, a `declearn` experiment is
+built in python.
+
+---
+**To see what this looks like in practice**, you can head to
+`examples/mnist/readme.md` in the `declearn` repository.
+
+---
+
+#### Stylized structure
+
+At a very high-level, declearn is structured around two key objects. The
+`Clients` hold the data and perform calculations locally. The `Server` owns
+the model and the global training process. They communicate through a `network`, hosted by the `Server`.
+
+We provide below a stylized view of the main elements of
+the `Server` and `Client` script. For more details, you can look at the
+hands on usage [section](user-guide/usage.md) of the documentation.
+
+We show what a `Client` and `Server` script can look like on a hypothetical
+LASSO logistic regression model, using a scikit-learn backend and
+pre-processed data. The data is in csv files with a "label" column,
 where each client has two files: one for training, the other for validation.
 
 Here, the code uses:
 
-- standard FedAvg strategy (SGD for local steps, averaging of updates weighted
-  by clients' training dataset size, no modifications of server-side updates)
-- 10 rounds of training, with 5 local epochs performed at each round and
-  128-samples batch size
-- at least 1 and at most 3 clients, awaited for 180 seconds by the server
-- network communications using gRPC, on host "example.com" and port 8888
-
-Note that this example code may easily be adjusted to suit use cases, using
-other types of models, alternative federated learning algorithms and/or
-modifying the communication, training and validation hyper-parameters.
-Please refer to the [Hands-on usage](./user-guide/usage.md) section for a more
-detailed and general description of how to set up a federated learning
-task and process with declearn.
+* **Aggregation**: the standard `FedAvg` strategy
+* **Optimizer**: standard SGD for both client and server
+* **Training**:
+  * $10$ rounds of training, with $5$ local epochs performed at each round and
+  $128$-samples batch size
+  * At least $1$ and at most $3$ clients, awaited for $180$ seconds by the
+  server
+* **Network**: communications using `websockets`
 
-## Server-side script
+The server-side script:
 
 ```python
 import declearn
@@ -41,7 +94,7 @@ model = declearn.model.sklearn.SklearnSGDModel.from_parameters(
     kind="classifier", loss="log_loss", penalty="l1"
 )
 netwk = declearn.communication.NetworkServerConfig(
-    protocol="grpc", host="example.com", port=8888,
+    protocol="websockets", host="127.0.0.1"", port=8888,
     certificate="path/to/certificate.pem",
     private_key="path/to/private_key.pem"
 )
@@ -60,14 +113,14 @@ config = declearn.main.config.FLRunConfig.from_params(
 server.run(config)
 ```
 
-## Client-side script
+The client-side script
 
 ```python
 import declearn
 
 netwk = declearn.communication.NetworkClientConfig(
-    protocol="grpc",
-    server_uri="example.com:8888",
+    protocol="websockets",
+    server_uri="127.0.0.1":8888",
     name="client_name",
     certificate="path/to/root_ca.pem"
 )
@@ -82,22 +135,165 @@ client = declearn.main.FederatedClient(
 client.run()
 ```
 
-## Simulating this experiment locally
-
-To simulate the previous experiment on a single computer, you may set up
-network communications to go through the localhost, and resort to one of
-two possibilities:
-
-1. Run the server and client-wise scripts parallelly, e.g. in distinct
-   terminals.
-2. Use declearn-provided tools to run the server and clients' routines
-   concurrently using multiprocessing.
-
-While technically similar (both solutions resolve on isolating the agents'
-routines in separate python processes that communicate over the localhost),
-the second solution offers more practicality in terms of offering a single
-entrypoint for your experiment, and optionally automatically stopping any
-running agent in case one of the other has failed.
-To find out more about this solution, please have a look at the Heart UCI
-example [implemented here](https://gitlab.inria.fr/magnet/declearn/declearn2\
--/tree/develop/examples/heart-uci).
+## 2. Federated learning on your own dataset
+
+### 2.1. Quickrun on your problem
+
+Using the mode `declearn-quickrun` requires a configuration file, some data, and a model:
+
+* A TOML file, to store your experiment configurations. In the MNIST example:
+`examples/mnist_quickrun/config.toml`.
+* A folder with your data, split by client. In the MNIST example, `examples/mnist_quickrun/data_iid` (after running `declearn-split --folder "examples/mnist_quickrun"`).
+* A model file, to store your model wrapped in a `declearn` object. In the
+MNIST example, `examples/mnist_quickrun/model.py`.
+
+#### The TOML file
+
+TOML is a minimal, human-readable configuration file format.
+We use is to store all the configurations of an FL experiment.
+The TOML is parsed by python as dictionnary with each `[header]`
+as a key. For more details, see the [TOML doc](https://toml.io/en/)
+
+This file is your main entry point to everything else.
+The absolute path to this file should be given as an argument in:
+
+```bash
+declearn-quickrun --config <path_to_toml_file>
+```
+
+The TOML file has six sections, some optional. Note the order does not matter,
+and that we give illustrative, not necessarily functionnal examples.
+
+`[network]`: Network configuration used by both client and server,
+most notably the port, host, and ssl certificates. An example:
+
+``` python
+[network]
+    protocol = "websockets" # Protocol used, to keep things simple use websocket
+    host = "127.0.0.1" # Address used, works as is on most set ups
+    port = 8765 # Port used, works as is on most set ups
+```
+
+This section is parsed as the initialization arguments to the `NetworkServer`
+class. Check its [documentation][declearn.communication.api.NetworkServer]
+to see all available fields. Note it is also used to initialize
+a [`NetworkClient`][declearn.communication.api.NetworkClient], mirroring the
+server.
+
+`[data]`: Where to find your data. This is particularly useful if you have split your data yourself, using custom names for files and folders. An example:
+
+```python
+[data]
+    data_folder = "./custom/data_custom" # Your main data folder
+    client_names = ["client_a","client_b","client_c"] # The names of your client folders
+    [dataset_names] # The names of train and test datasets
+    train_data = "cifar_train"
+    train_target = "label_train"
+    valid_data = "cifar_valid"
+    valid_target = "label_valid"
+```
+
+This section is parsed as the fields of a `DataSourceConfig` dataclass. Check its
+[documentation][declearn.quickrun.DataSourceConfig] to see all
+available fields. This `DataSourceConfig` is then parsed by the
+[`parse_data_folder`][`declearn.quickrun.parse_data_folder`] function.
+
+`[optim]`: Optimization options for both client and server, with three distinct sub-sections : the server-side aggregator (i) and optimizer (ii), and the client optimizer (iii). An example:
+
+```python
+[optim]
+    aggregator = "averaging" # The basic server aggregation strategy
+
+    [optim.server_opt] # Server optimization strategy
+    lrate = 1.0 # Server learning rate
+
+    [optim.client_opt] # Client optimization strategy
+    lrate = 0.001 # Client learning rate
+    modules = [["momentum", {"beta" = 0.9}]] # List of optimizer modules used
+    regularizers = [["lasso", {alpha = 0.1}]] # List of regularizer modules
+```
+
+This section is parsed as the fields of a `FLOptimConfig` dataclass. Check its
+[documentation][declearn.main.FLOptimConfig] to see more details on the three
+sub-sections. For more details on available fields within those subsections, you
+can naviguate inside the documentation of the [`Aggregator`][declearn.aggregator.Aggregator] and [`Optimizer`][declearn.optimizer.Optimizer] classes.
+
+`[run]`: Training process option for both client and server. Most notably, includes the number of rounds as well as the registration, training, and evaluation parameters. An example:
+
+```python
+[run]
+    rounds = 10 # Number of overall training rounds
+
+    [run.register] # Client registration options
+    min_clients = 1 # Minimum of clients that need to connect
+    max_clients = 6 # The maximum number of clients that can connect
+    timeout = 5 # How long to wait for clients, in seconds
+
+    [run.training] # Client training procedure
+    n_epoch = 1 # Number of local epochs
+    batch_size = 48 # Training batch size
+    drop_remainder = false # Whether to drop the last training examples
+
+    [run.evaluate]
+    batch_size = 128 # Evaluation batch size
+```
+
+This section is parsed as the fields of a `FLRunConfig` dataclass. Check its
+[documentation][declearn.main.FLOptimConfig] to see more details on the
+sub-sections. For more details on available fields within those subsections, you
+can naviguate inside the documentation of `FLRunConfig` to the relevant
+dataclass, for instance [`TrainingConfig`][declearn.main.TrainingConfig]
+
+`[model]`: Optional section, where to find the model. An example:
+
+```python
+[model] 
+# The location to a model file
+model_file = "./custom/model_custom.py"
+# The name of your model file, if different from "MyModel"
+model_name = "MyCustomModel" 
+```
+
+This section is parsed as the fields of a `ModelConfig` dataclass. Check its
+[documentation][declearn.quickrun.ModelConfig] to see all
+available fields.
+
+`[experiment]`: Optional section, what to report during the experiment and where to report it. An example:
+
+```python
+[experiment] 
+metrics=[["multi-classif",{labels = [0,1,2,3,4,5,6,7,8,9]}]] # Accuracy metric
+checkpoint = "./result_custom" # Custom location for results
+```
+
+This section is parsed as the fields of a `ExperimentConfig` dataclass. Check its
+[documentation][declearn.quickrun.ExperimentConfig] to see all
+available fields.
+
+#### The data
+
+Your data, in a standard tabular format, split by client. Within each client
+folder, we expect four files : training data and labels, validation data and
+labels.
+
+If your data is not already split by client, we are developping an experimental
+data splitting utility. It currently has a limited scope, only dealing
+with classification tasks, excluding multi-label. You can call it using
+`declearn-split --folder <path_to_original_data>`. For more details, refer to
+the [documentation][declearn.dataset.split_data].
+
+#### The Model file
+
+The model file should just contain the model you built for
+your data, e.g. a `torch` model, wrapped in a declearn object.
+See `examples/mnist_quickrun/model.py` for an example.
+
+The wrapped model should be named "MyModel" by default. If you use
+any other name, you'll need to mention it in the TOML file, as done
+in `./custom/config_custom.toml`
+
+### 2.2. Using declearn full capabilities
+
+To upgrade your experimental setting beyond the `quickrun` mode,
+head to the hands on usage [section](user-guide/usage.md) of the
+documentation.
diff --git a/docs/setup.md b/docs/setup.md
index 547f44b1126135e50cdf39724d0c2d178a623cdf..805f6d140f4794965f404fdbbdd6da5282e821bb 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -107,3 +107,5 @@ pip install declearn[all,tests]  # install all extra and testing dependencies
   package, and then to manually install the dependencies listed in the
   `pyproject.toml` file, using `conda install` rather than `pip install`
   whenever it is possible.
+- On some systems, the square brackets used our pip install are not properly
+parsed. Try replacing `[` by `\[` and `]` by `\]`.
diff --git a/examples/mnist_quickrun/config.toml b/examples/mnist_quickrun/config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b01422a743b1631857f799686e13be08f60df34b
--- /dev/null
+++ b/examples/mnist_quickrun/config.toml
@@ -0,0 +1,41 @@
+# This is a minimal TOML file for the MNIST example
+# It contains the bare minimum to make the experiment run.
+# See quickstart for more details. 
+
+# The TOML is parsed by python as dictionnary with each `[header]`
+# as a key. Note the "=" sign and the absence of quotes around keys.
+# For more details, see the full doc : https://toml.io/en/
+
+[network] # Network configuration used by both client and server
+    protocol = "websockets" # Protocol used, to keep things simple use websocket
+    host = "127.0.0.1" # Address used, works as-is on most set ups
+    port = 8765 # Port used, works as-is on most set ups
+
+[data] # Where to find your data
+    data_folder = "examples/mnist_quickrun/data_iid" 
+
+[optim] # Optimization options for both client and server
+    aggregator = "averaging" # Server aggregation strategy
+
+    [optim.client_opt] # Client optimization strategy
+    lrate = 0.001 # Client learning rate
+    modules = ["adam"] # List of optimizer modules used
+
+    [optim.server_opt] # Server optimization strategy
+    lrate = 1.0 # Server learning rate
+
+[run] # Training process option for both client and server
+    rounds = 10 # Number of overall training rounds
+
+    [run.register] # Client registration options
+    timeout = 5 # How long to wait for clients, in seconds
+
+    [run.training] # Client training options
+    batch_size = 48 # Training batch size
+
+    [run.evaluate] # Client evaluation options
+    batch_size = 128 # Evaluation batch size
+
+[experiment] # What to report during the experiment and where to report it
+    metrics=[["multi-classif",{labels = [0,1,2,3,4,5,6,7,8,9]}]] # Accuracy metric
+
diff --git a/examples/mnist_quickrun/mnist.ipynb b/examples/mnist_quickrun/mnist.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..7753314dcda05051be7177eb88705051a07d3188
--- /dev/null
+++ b/examples/mnist_quickrun/mnist.ipynb
@@ -0,0 +1,576 @@
+{
+  "cells": [
+    {
+      "attachments": {},
+      "cell_type": "markdown",
+      "metadata": {},
+      "source": [
+        "This notebook is meant to be run in google colab. You can find import your local copy of the file in the the [colab welcome page](https://colab.research.google.com/)."
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "s9bpLdH5ThpJ"
+      },
+      "source": [
+        "# Setting up your declearn "
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "Clzf4NTja121"
+      },
+      "source": [
+        "We first clone the repo, to have both the package itself and the `examples` folder we will use in this tutorial, then naviguate to the package directory, and finally install the required dependencies"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "u2QDwb0_QQ_f",
+        "outputId": "cac0761c-b229-49b0-d71d-c7b5cef919b3"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "Cloning into 'declearn2'...\n",
+            "warning: redirecting to https://gitlab.inria.fr/magnet/declearn/declearn2.git/\n",
+            "remote: Enumerating objects: 4997, done.\u001b[K\n",
+            "remote: Counting objects: 100% (79/79), done.\u001b[K\n",
+            "remote: Compressing objects: 100% (79/79), done.\u001b[K\n",
+            "remote: Total 4997 (delta 39), reused 0 (delta 0), pack-reused 4918\u001b[K\n",
+            "Receiving objects: 100% (4997/4997), 1.15 MiB | 777.00 KiB/s, done.\n",
+            "Resolving deltas: 100% (3248/3248), done.\n"
+          ]
+        }
+      ],
+      "source": [
+        "!git clone -b experimental https://gitlab.inria.fr/magnet/declearn/declearn2"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "9kDHh_AfPG2l",
+        "outputId": "74e2f85f-7f93-40ae-a218-f4403470d72c"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "/content/declearn2\n"
+          ]
+        }
+      ],
+      "source": [
+        "cd declearn2"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "Un212t1GluHB",
+        "outputId": "0ea67577-da6e-4f80-a412-7b7a79803aa1"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
+            "Processing /content/declearn2\n",
+            "  Installing build dependencies ... \u001b[?25l\u001b[?25hdone\n",
+            "  Getting requirements to build wheel ... \u001b[?25l\u001b[?25hdone\n",
+            "  Installing backend dependencies ... \u001b[?25l\u001b[?25hdone\n",
+            "  Preparing metadata (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+            "Requirement already satisfied: cryptography>=35.0 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (40.0.1)\n",
+            "Requirement already satisfied: scikit-learn>=1.0 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (1.2.2)\n",
+            "Requirement already satisfied: requests~=2.18 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (2.27.1)\n",
+            "Requirement already satisfied: pandas>=1.2 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (1.5.3)\n",
+            "Requirement already satisfied: tomli>=2.0 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (2.0.1)\n",
+            "Collecting fire>=0.4\n",
+            "  Downloading fire-0.5.0.tar.gz (88 kB)\n",
+            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m88.3/88.3 kB\u001b[0m \u001b[31m1.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+            "\u001b[?25h  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+            "Requirement already satisfied: typing-extensions>=4.0 in /usr/local/lib/python3.9/dist-packages (from declearn==2.1.0) (4.5.0)\n",
+            "Collecting websockets~=10.1\n",
+            "  Downloading websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (106 kB)\n",
+            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m106.5/106.5 kB\u001b[0m \u001b[31m3.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
+            "\u001b[?25hRequirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.9/dist-packages (from cryptography>=35.0->declearn==2.1.0) (1.15.1)\n",
+            "Requirement already satisfied: six in /usr/local/lib/python3.9/dist-packages (from fire>=0.4->declearn==2.1.0) (1.16.0)\n",
+            "Requirement already satisfied: termcolor in /usr/local/lib/python3.9/dist-packages (from fire>=0.4->declearn==2.1.0) (2.2.0)\n",
+            "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/dist-packages (from pandas>=1.2->declearn==2.1.0) (2022.7.1)\n",
+            "Requirement already satisfied: numpy>=1.20.3 in /usr/local/lib/python3.9/dist-packages (from pandas>=1.2->declearn==2.1.0) (1.22.4)\n",
+            "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.9/dist-packages (from pandas>=1.2->declearn==2.1.0) (2.8.2)\n",
+            "Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests~=2.18->declearn==2.1.0) (2.0.12)\n",
+            "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.9/dist-packages (from requests~=2.18->declearn==2.1.0) (1.26.15)\n",
+            "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests~=2.18->declearn==2.1.0) (2022.12.7)\n",
+            "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests~=2.18->declearn==2.1.0) (3.4)\n",
+            "Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=1.0->declearn==2.1.0) (1.10.1)\n",
+            "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=1.0->declearn==2.1.0) (3.1.0)\n",
+            "Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=1.0->declearn==2.1.0) (1.2.0)\n",
+            "Requirement already satisfied: pycparser in /usr/local/lib/python3.9/dist-packages (from cffi>=1.12->cryptography>=35.0->declearn==2.1.0) (2.21)\n",
+            "Building wheels for collected packages: fire, declearn\n",
+            "  Building wheel for fire (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+            "  Created wheel for fire: filename=fire-0.5.0-py2.py3-none-any.whl size=116952 sha256=ab01943c400d3267450974ec56a6572193bed40710845edd44623e56c7757799\n",
+            "  Stored in directory: /root/.cache/pip/wheels/f7/f1/89/b9ea2bf8f80ec027a88fef1d354b3816b4d3d29530988972f6\n",
+            "  Building wheel for declearn (pyproject.toml) ... \u001b[?25l\u001b[?25hdone\n",
+            "  Created wheel for declearn: filename=declearn-2.1.0-py3-none-any.whl size=276123 sha256=4969a91ded8b704c8c9497bcda8f514f847c49098715d659cc8e96a947ec887f\n",
+            "  Stored in directory: /tmp/pip-ephem-wheel-cache-fgkx9jiw/wheels/cc/79/79/6586306a117d40a1f8b251a22e50583b8abb2d7e855a62ecf7\n",
+            "Successfully built fire declearn\n",
+            "Installing collected packages: websockets, fire, declearn\n",
+            "Successfully installed declearn-2.1.0 fire-0.5.0 websockets-10.4\n"
+          ]
+        }
+      ],
+      "source": [
+        "!pip install .[websockets]"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "hC8Fty8YTy9P"
+      },
+      "source": [
+        "# Running your first experiment"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "rcWcZJdob1IG"
+      },
+      "source": [
+        "We are going to train a common model between three simulated clients on the classic [MNIST dataset](http://yann.lecun.com/exdb/mnist/). The input of the model is a set of images of handwritten digits, and the model needs to determine which number between 0 and 9 each image corresponds to."
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "KlY_vVtFHv2P"
+      },
+      "source": [
+        "## The model\n",
+        "\n",
+        "To do this, we will use a simple CNN, defined in `examples/mnist_quickrun/model.py`"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "C7D52a8_dEr7",
+        "outputId": "a25223f8-c8eb-4998-d7fd-4b8bfde92486"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "Model: \"sequential\"\n",
+            "_________________________________________________________________\n",
+            " Layer (type)                Output Shape              Param #   \n",
+            "=================================================================\n",
+            " conv2d (Conv2D)             (None, 26, 26, 8)         80        \n",
+            "                                                                 \n",
+            " max_pooling2d (MaxPooling2D  (None, 13, 13, 8)        0         \n",
+            " )                                                               \n",
+            "                                                                 \n",
+            " dropout (Dropout)           (None, 13, 13, 8)         0         \n",
+            "                                                                 \n",
+            " flatten (Flatten)           (None, 1352)              0         \n",
+            "                                                                 \n",
+            " dense (Dense)               (None, 64)                86592     \n",
+            "                                                                 \n",
+            " dropout_1 (Dropout)         (None, 64)                0         \n",
+            "                                                                 \n",
+            " dense_1 (Dense)             (None, 10)                650       \n",
+            "                                                                 \n",
+            "=================================================================\n",
+            "Total params: 87,322\n",
+            "Trainable params: 87,322\n",
+            "Non-trainable params: 0\n",
+            "_________________________________________________________________\n"
+          ]
+        },
+        {
+          "name": "stderr",
+          "output_type": "stream",
+          "text": [
+            "/content/declearn2/declearn/model/tensorflow/utils/_gpu.py:66: UserWarning: Cannot use a GPU device: either CUDA is unavailable or no GPU is visible to tensorflow.\n",
+            "  warnings.warn(\n"
+          ]
+        }
+      ],
+      "source": [
+        "from examples.mnist_quickrun.model import model\n",
+        "model.summary()"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "HoBcOs9hH2QA"
+      },
+      "source": [
+        "## The data\n",
+        "\n",
+        "We start by splitting the MNIST dataset between 3 clients and storing the output in the `examples/mnist_quickrun` folder. For this we use an experimental utility provided by `declearn`. "
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "quduXkpIWFjL",
+        "outputId": "ddf7d45d-acf0-44ee-ce77-357c0987a2a1"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "Downloading MNIST source file train-images-idx3-ubyte.gz.\n",
+            "Downloading MNIST source file train-labels-idx1-ubyte.gz.\n",
+            "Splitting data into 3 shards using the 'iid' scheme.\n"
+          ]
+        }
+      ],
+      "source": [
+        "from declearn.dataset import split_data\n",
+        "split_data(folder=\"examples/mnist_quickrun\")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "3-2hKmz-2RF4"
+      },
+      "source": [
+        "Here is what the first image of the first client looks like:"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 430
+        },
+        "id": "MLVI9GOZ1TGd",
+        "outputId": "f34a6a93-cb5f-4a45-bc24-4146ea119d1a"
+      },
+      "outputs": [
+        {
+          "data": {
+            "image/png": "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",
+            "text/plain": [
+              "<Figure size 640x480 with 1 Axes>"
+            ]
+          },
+          "metadata": {},
+          "output_type": "display_data"
+        }
+      ],
+      "source": [
+        "import matplotlib.pyplot as plt\n",
+        "import numpy as np\n",
+        "\n",
+        "images = np.load(\"examples/mnist_quickrun/data_iid/client_0/train_data.npy\")\n",
+        "sample_img = images[0]\n",
+        "sample_fig = plt.imshow(sample_img,cmap='Greys')\n"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "1vNWNGjefSfH"
+      },
+      "source": [
+        "For more information on how the `split_data` function works, you can look at the documentation. "
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/"
+        },
+        "id": "-wORmq5DYfRF",
+        "outputId": "4d79da63-ccad-4622-e600-ac36fae1ff3f"
+      },
+      "outputs": [
+        {
+          "name": "stdout",
+          "output_type": "stream",
+          "text": [
+            "Randomly split a dataset into shards.\n",
+            "\n",
+            "    The resulting folder structure is :\n",
+            "        folder/\n",
+            "        └─── data*/\n",
+            "            └─── client*/\n",
+            "            │      train_data.* - training data\n",
+            "            │      train_target.* - training labels\n",
+            "            │      valid_data.* - validation data\n",
+            "            │      valid_target.* - validation labels\n",
+            "            └─── client*/\n",
+            "            │    ...\n",
+            "\n",
+            "    Parameters\n",
+            "    ----------\n",
+            "    folder: str, default = \".\"\n",
+            "        Path to the folder where to add a data folder\n",
+            "        holding output shard-wise files\n",
+            "    data_file: str or None, default=None\n",
+            "        Optional path to a folder where to find the data.\n",
+            "        If None, default to the MNIST example.\n",
+            "    target_file: str or int or None, default=None\n",
+            "        If str, path to the labels file to import. If int, column of\n",
+            "        the data file to be used as labels. Required if data is not None,\n",
+            "        ignored if data is None.\n",
+            "    n_shards: int\n",
+            "        Number of shards between which to split the data.\n",
+            "    scheme: {\"iid\", \"labels\", \"biased\"}, default=\"iid\"\n",
+            "        Splitting scheme(s) to use. In all cases, shards contain mutually-\n",
+            "        exclusive samples and cover the full raw training data.\n",
+            "        - If \"iid\", split the dataset through iid random sampling.\n",
+            "        - If \"labels\", split into shards that hold all samples associated\n",
+            "        with mutually-exclusive target classes.\n",
+            "        - If \"biased\", split the dataset through random sampling according\n",
+            "        to a shard-specific random labels distribution.\n",
+            "    perc_train:  float, default= 0.8\n",
+            "        Train/validation split in each client dataset, must be in the\n",
+            "        ]0,1] range.\n",
+            "    seed: int or None, default=None\n",
+            "        Optional seed to the RNG used for all sampling operations.\n",
+            "    \n"
+          ]
+        }
+      ],
+      "source": [
+        "print(split_data.__doc__)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "kZtbxlwUftKd"
+      },
+      "source": [
+        "## Quickrun\n",
+        "\n",
+        "We can now run our experiment. As explained in the section 2.1 of the [quickstart documentation](https://magnet.gitlabpages.inria.fr/declearn/docs/2.1/quickstart), using the mode `declearn-quickrun` requires a configuration file, some data, and a model:\n",
+        "\n",
+        "* A TOML file, to store your experiment configurations. Here: \n",
+        "`examples/mnist_quickrun/config.toml`.\n",
+        "* A folder with your data, split by client. Here: `examples/mnist_quickrun/data_iid`\n",
+        "* A model file, to store your model wrapped in a `declearn` object. Here: `examples/mnist_quickrun/model.py`.\n",
+        "\n",
+        "We then only have to run the `quickrun` util with the path to the TOML file:"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "id": "1n_mvTIIWpRf"
+      },
+      "outputs": [],
+      "source": [
+        "from declearn.quickrun import quickrun\n",
+        "quickrun(config=\"examples/mnist_quickrun/config.toml\")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "O0kuw7UxJqKk"
+      },
+      "source": [
+        "The output obtained is the combination of the CLI output of our server and our clients, going through: \n",
+        "\n",
+        "* `INFO:Server:Starting clients registration process.` : a first registration step, where clients register with the server\n",
+        "* `INFO:Server:Sending initialization requests to clients.`: the initilization of the object needed for training on both the server and clients side.\n",
+        "* `Server:INFO: Initiating training round 1`: the training starts, where each client makes its local update(s) and send the result to the server which aggregates them\n",
+        "* `INFO: Initiating evaluation round 1`: the model is evaluated at each round\n",
+        "* `Server:INFO: Stopping training`: the training is finalized  "
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "wo6NDugiOH6V"
+      },
+      "source": [
+        "## Results \n",
+        "\n",
+        "You can have a look at the results in the `examples/mnist_quickrun/result_*` folder, including the metrics evolution during training. "
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "id": "zlm5El13SvnG"
+      },
+      "outputs": [],
+      "source": [
+        "import pandas as pd\n",
+        "import glob\n",
+        "import os \n",
+        "\n",
+        "res_file = glob.glob('examples/mnist_quickrun/result*') \n",
+        "res = pd.read_csv(os.path.join(res_file[0],'server/metrics.csv'))\n",
+        "res_fig = res.plot()"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "Kd_MBQt9OJ40"
+      },
+      "source": [
+        "# Experiment further\n",
+        "\n",
+        "\n",
+        "You can change the TOML config file to experiment with different strategies."
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "E3OOeAYJRGqU"
+      },
+      "source": [
+        "For instance, try splitting the data in a very heterogenous way, by distributing digits in mutually exclusive way between clients. "
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "id": "BNPLnpQuQ8Au"
+      },
+      "outputs": [],
+      "source": [
+        "split_data(folder=\"examples/mnist_quickrun\",scheme='labels')"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "Xfs-3wH-3Eio"
+      },
+      "source": [
+        "And change the `examples/mnist_quickrun/config.toml` file with:\n",
+        "\n",
+        "```\n",
+        "[data] \n",
+        "    data_folder = \"examples/mnist_quickrun/data_labels\" \n",
+        "```"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "ZZVFNO07O1ry"
+      },
+      "source": [
+        "If you run the model as is, you should see a drop of performance\n",
+        "\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "id": "7kFa0EbINJXq"
+      },
+      "outputs": [],
+      "source": [
+        "quickrun(config=\"examples/mnist_quickrun/config.toml\")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "XV6JfaRzR3ee"
+      },
+      "source": [
+        "Now try modifying the `examples/mnist_quickrun/config.toml` file like this, to implement the [scaffold algorithm](https://arxiv.org/abs/1910.06378) and running the experiment again. \n",
+        "\n",
+        "```\n",
+        "  [optim]\n",
+        "\n",
+        "      [optim.client_opt]\n",
+        "      lrate = 0.005 \n",
+        "      modules = [\"scaffold-client\"] \n",
+        "\n",
+        "      [optim.server_opt]\n",
+        "      lrate = 1.0 \n",
+        "      modules = [\"scaffold-client\"]\n",
+        "```"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": null,
+      "metadata": {
+        "id": "FK6c9HDjSdGZ"
+      },
+      "outputs": [],
+      "source": [
+        "quickrun(config=\"examples/mnist_quickrun/config.toml\")"
+      ]
+    }
+  ],
+  "metadata": {
+    "colab": {
+      "collapsed_sections": [
+        "s9bpLdH5ThpJ",
+        "KlY_vVtFHv2P",
+        "HoBcOs9hH2QA",
+        "kZtbxlwUftKd",
+        "wo6NDugiOH6V",
+        "Kd_MBQt9OJ40"
+      ],
+      "provenance": []
+    },
+    "kernelspec": {
+      "display_name": "Python 3",
+      "name": "python3"
+    },
+    "language_info": {
+      "name": "python"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 0
+}
diff --git a/examples/quickrun/model.py b/examples/mnist_quickrun/model.py
similarity index 100%
rename from examples/quickrun/model.py
rename to examples/mnist_quickrun/model.py
diff --git a/examples/quickrun/config.toml b/examples/quickrun/config.toml
deleted file mode 100644
index 35c23aebda424df9cd53372d6dfc87dde084f21b..0000000000000000000000000000000000000000
--- a/examples/quickrun/config.toml
+++ /dev/null
@@ -1,39 +0,0 @@
-# This is a minimal TOML file for the MNIST example
-# It contains the bare minimum to make the experiment run
-# It can be used as a template for other experiments.
-# See `examples/quickrun/readme.md` for more details.
-
-
-[network] # Network configuration used by both client and server
-protocol = "websockets" # Protocol used, to keep things simple use websocket
-host = "127.0.0.1" # Address used, works as is on most set ups
-port = 8765 # Port used, works as is on most set ups
-
-[model] # Information on where to find the model file
-
-[data] # How to split your data
-scheme = "iid" # SPlit your data iid between simulated clients
-
-[optim] # Optimizers options for both client and server
-aggregator = "averaging" # Server aggregation strategy
-
-    [optim.client_opt] # Client optimization strategy
-    lrate = 0.001 # Client learning rate
-    modules = ["adam"] # List of optimizer modules used
-
-[run] # Training process option for both client and server
-rounds = 10 # Number of overall training rounds
-
-    [run.register] # Client registration options
-    timeout = 5 # How long to wait for clients, in seconds
-
-    [run.training] # Client training procedure
-    batch_size = 48 # Training batch size
-
-    [run.evaluate]
-    # batch_size = 128 # Evaluation batch size
-
-
-[experiment] # What to report during the experiment and where to report it
-metrics=[["multi-classif",{labels = [0,1,2,3,4,5,6,7,8,9]}]] # Accuracy metric
-
diff --git a/examples/quickrun/custom/config_custom.toml b/examples/quickrun/custom/config_custom.toml
deleted file mode 100644
index 73c0c9e5e1e5fdf551985019eb67962907e57819..0000000000000000000000000000000000000000
--- a/examples/quickrun/custom/config_custom.toml
+++ /dev/null
@@ -1,81 +0,0 @@
-# This is a TOML file for demonstrating the extend of customization 
-# available for experiments through TOMl. It contains commented mock
-# fields and can be used as a template for other experiments. It will
-# not run as is as if references some not existing files for demonstration 
-# purposes.
-
-
-[network] # Network configuration used by both client and server
-protocol = "websockets" # Protocol used, to keep things simple use websocket
-host = "127.0.0.1" # Address used, works as is on most set ups
-port = 8765 # Port used, works as is on most set ups
-
-[model] # Information on where to find the model file
-# The location to a model file, if not following expected structure
-model_file = "examples/quickrun/custom/model_custom.py"
-# The name of your model file, if different from "MyModel"
-model_name = "MyCustomModel" 
-
-[data] # How to split your data
-
-# If you want to split your data 
-# Where to save your split data, if not in the expected "result" folder
-data_folder = "examples/quickrun/custom/data_custom"
-n_shards = 3 # Number of simulated clients
-# Where to find the original data
-data_file = "examples/quickrun/data_unsplit/train_data.npy"
-# Where to find the original data labels
-label_file = "examples/quickrun/data_unsplit/train_target.npy"
-scheme = "iid" # How to split your data between simulated clients
-perc_train = 0.8 # For each client, how much data 
-seed = 22
-
-# If you have already split data, not using expected names 
-# The custom names of your clients
-client_names = ["client_0","client_1","client_2"]
-    [dataset_names] # The names train and test datasets
-    train_data = "train_data" 
-    train_target = "train_target"
-    valid_data = "valid_data"
-    valid_target = "valid_target"
-
-[optim] # Optimizers options for both client and server
-aggregator = "averaging" # Server aggregation strategy
-server_opt = 1.0 # The server learning rate
-
-    [optim.client_opt] # Client optimization strategy
-    lrate = 0.001 # Client learning rate
-    # List of optimizer modules used
-    modules = [["momentum", {"beta" = 0.9}]]
-    # List of regularizer modules
-    regularizers = [["lasso", {alpha = 0.1}]]
-
-[run] # Training process option for both client and server
-rounds = 10 # Number of overall training rounds
-
-    [run.register] # Client registration options
-    min_clients = 1 # Minimum of clients that need to connect
-    max_clients = 6 # The maximum number of clients that can connect
-    timeout = 5 # How long to wait for clients, in seconds
-
-    [run.training] # Client training procedure
-    n_epoch = 1 # Number of local epochs
-    batch_size = 48 # Training batch size
-    drop_remainder = false # Whether to drop the last trainig examples
-
-    [run.evaluate]
-    batch_size = 128 # Evaluation batch size
-
-
-[experiment] # What to report during the experiment and where to report it
-metrics=[["multi-classif",{labels = [0,1,2,3,4,5,6,7,8,9]}]] # Accuracy metric
-checkpoint = "examples/quickrun/result_custom" # Custom location to results
-
-
-
-
-
-
-
-
-
diff --git a/examples/quickrun/custom/model_custom.py b/examples/quickrun/custom/model_custom.py
deleted file mode 100644
index 4aaef2c33527b260fe04187cb0a65a7a8f662624..0000000000000000000000000000000000000000
--- a/examples/quickrun/custom/model_custom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Wrapping a torch model"""
-
-import tensorflow as tf
-import torch
-import torch.nn as nn
-import torch.nn.functional as F
-
-from declearn.model.tensorflow import TensorflowModel
-from declearn.model.torch import TorchModel
-
-
-stack = [
-    tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
-    tf.keras.layers.Conv2D(32, 3, 1, activation="relu"),
-    tf.keras.layers.Conv2D(64, 3, 1, activation="relu"),
-    tf.keras.layers.MaxPool2D(2),
-    tf.keras.layers.Dropout(0.25),
-    tf.keras.layers.Flatten(),
-    tf.keras.layers.Dense(128, activation="relu"),
-    tf.keras.layers.Dropout(0.5),
-    tf.keras.layers.Dense(10, activation="softmax"),
-]
-model = tf.keras.models.Sequential(stack)
-MyCustomModel = TensorflowModel(model, loss="sparse_categorical_crossentropy")
diff --git a/examples/quickrun/readme.md b/examples/quickrun/readme.md
deleted file mode 100644
index 2eb05b6444cc95f981a1956b45d9d7e7664eb16a..0000000000000000000000000000000000000000
--- a/examples/quickrun/readme.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Using quickrun
-
-## The main idea
-
-The `quickrun` mode is a way to quickly run an FL experiment
-without needing to understand the details of `declearn`.
-
-Once you have `declearn` installed, try running `declearn-quickrun`
-to get run the MNIST example.
-
-This mode requires two files and some data :
-
-* A TOML file, to store your experiment configurations.
-* A model file, to store your model wrapped in a `declearn` object
-* A folder with your data, either already split between clients or
-to be split usiong our utility function
-
-To run your own expriments, these three elements needs to be either
-organizzed in a specific way, or referenced in the TOML file. See details
-in the last section.
-
-## The TOML file
-
-TOML is a minimal, human-readable configuration file format.
-We use is to store all the configurations of an FL experiment.
-
-This file is your main entry point to everythiong else.
-If you write your own, the absolute path to this file should be given
-as an argument :  `declearn-quickrun --config <path_to_toml_file>`
-
-For a minimal example of what it looks like in `declearn`, see
-`./config.toml`. You can use it as a template
-to write your own.
-
-The TOML is parsed to python as dictionnary with each `[header]`
-as a key. If you are a first time user, you just need to
-understand how to write dictionnaries and lists. This :
-
-```
-[key1]
-sub_key = ["a","b"]
-[key2]
-sub_key = 1
-```
-
-will be parsed as :
-
-```python
-{"key1":{"sub_key":["a","b"]},"key2":{"sub_key":1}}
-```
-
-Note the = sign and the absence of quotes around keys.
-
-For more details, see the full doc : <https://toml.io/en/>
-
-For a more detailed templates, with all options used
-See `./custom/config_custom.toml`
-
-## The Model file
-
-The model file should just contain the model you built for
-your data, e.g. a `torch` model, wrapped in a declearn object.
-See `./model.py` for an example.
-
-The wrapped model should be named "MyModel" by default. If you use
-any other name, you'll need to mention it in the TOML file, as done
-in `./custom/config_custom.toml`
-
-## The data
-
-Your data, in a standard tabular format. This data can either require
-splitting or be already split by client. If your data requires splitting,
-we provide with an experimental data splitter with currently a limited
-scope.
-
-Requires splitting:
-
-* You have a single dataset and want to use provided utils to split it
-between simulated clients
-* In which case you need to mention your data source in the TOML file,
-as well as details on how to split your data. See
-`./custom/config_custom.toml` for details.
-* Note that our data splitting util currently has a limited scope,
-only dealing with classification tasks, excluding multi-label. If your
-use case falls outside of that, you can write custom splitting code
-and refer to the next paragraph
-
-Already split:
-
-* If your data is already split between clients, you will need to
-add details in the TOML file on where to find this data. See
-`./custom/config_custom.toml` for details.
-
-## Organizing your files
-
-The quickrun mode expects a `config` path as an argument. This can be the path to :
-
-* A folder, expected to be structured a certain way
-* A TOML file, where the location of other object is mentionned
-
-In both cases, the default is to check the folder provided, or the TOML
-parent folder, is structured as follows:
-
-```
-    folder/
-    │    config.toml - the config file
-    │    model.py - the model
-    └─── data*/ - a folder starting with 'data' containing split data
-        └─── client*/ - one folder per client, each containing 4 files
-        │      train_data.* - training data
-        │      train_target.* - training labels
-        │      valid_data.* - validation data
-        │      valid_target.* - validation labels
-        └─── client*/
-        │    ...
-    └─── result/
-        └─── client*/
-        ...
-```
-
-Any changes to this structure should be referenced in the TOML file, as
-shown in `./custom/config_custom.toml`.
diff --git a/pyproject.toml b/pyproject.toml
index 4e008541bd004462d4b8e304f4b1f389b583d6df..140a4aa184e8a1e4cb64917e73a8023d597a70c2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,6 +42,7 @@ dependencies = [
     "scikit-learn >= 1.0",
     "tomli >= 2.0 ; python_version < '3.11'",
     "typing_extensions >= 4.0",
+    "fire >= 0.4",
 ]
 
 [project.optional-dependencies]
@@ -132,3 +133,4 @@ declearn = ["py.typed"]
 
 [project.scripts]
 declearn-quickrun = "declearn.quickrun.run:main"
+declearn-split = "declearn.dataset.split_data:main"