diff --git a/docs/source/topics/exceptions.rst b/docs/source/topics/exceptions.rst
index e55e3af..551c495 100644
--- a/docs/source/topics/exceptions.rst
+++ b/docs/source/topics/exceptions.rst
@@ -201,6 +201,28 @@ Handling Multiple Exceptions
     Finally, we recommend using ``Concurrent[...]`` only if you want to suppress
     concurrent exceptions unconditionally.
 
+Propagating Exceptions Best-Practices
+-------------------------------------
+
+.. content-tabs:: left-col
+
+    Whether an :term:`activity` uses concurrency or not is usually not obvious.
+    Unless explicitly documented, a :py:exc:`~usim.Concurrent` exception propagating
+    out of an :term:`activity` is likely unexpected.
+    As such, avoid propagating :py:exc:`~usim.Concurrent` exceptions whenever possible!
+
+    All abstractions in μSim convert internal :py:exc:`~usim.Concurrent` exceptions
+    to regular exceptions.
+    Only :py:class:`~usim.Scope` and derived types may raise
+    :py:exc:`~usim.Concurrent` exceptions when exiting an ``async with`` context.
+    The intention is to hide implementation details,
+    while allowing full control on expected concurrency.
+
+    We strongly recommend to similarly avoid propagating
+    :py:exc:`~usim.Concurrent` exceptions if possible.
+    When unavoidable, do not expose nested concurrency but
+    propagate a :py:meth:`~usim.Concurrent.flattened` exception.
+
 
     .. [#debug] For the use of :py:exc:`AssertionError` by μSim,
                 see also :doc:`./debug`.
diff --git a/usim/_primitives/concurrent_exception.py b/usim/_primitives/concurrent_exception.py
index 02ab97c..b64bb04 100644
--- a/usim/_primitives/concurrent_exception.py
+++ b/usim/_primitives/concurrent_exception.py
@@ -14,7 +14,7 @@ class MetaConcurrent(type):
     # metaclass instance fields - i.e. class fields
     # used to define specialisations of a base type
     inclusive: bool
-    specialisations: Optional[Tuple[Type[Exception]]]
+    specialisations: 'Optional[Tuple[Type[Union[Concurrent, Exception]], ...]]'
     template: 'MetaConcurrent'
     __specialisations__: WeakValueDictionary
 
@@ -34,7 +34,7 @@ class MetaConcurrent(type):
         bases: Tuple[Type, ...],
         namespace: Dict[str, Any],
         specialisations:
-            Optional[Tuple[Type[Exception], ...]] = None,
+            'Optional[Tuple[Type[Union[Concurrent, Exception]], ...]]' = None,
         inclusive: bool = True,
         **kwargs,
     ):
@@ -142,8 +142,9 @@ class MetaConcurrent(type):
         item:  # [Exception] or [...] or [Exception, ...]
             Union[
                 Type[Exception],
+                'Type[Concurrent]',
                 'ellipsis',
-                Tuple[Union[Type[Exception], 'ellipsis'], ...]
+                'Tuple[Union[Type[Concurrent], Type[Exception], "ellipsis"], ...]',
             ]
     ):
         """``cls[item]`` - used to specialise ``cls`` with ``item``"""
@@ -157,15 +158,19 @@ class MetaConcurrent(type):
             return cls
         # Cls[item]
         elif type(item) is not tuple:
-            assert issubclass(item, Exception),\
-                f'{cls.__name__!r} may only be specialised by Exception subclasses'
+            assert issubclass(item, (Exception, cls)), (
+                f'{cls.__name__!r} may only be specialised by Exception subclasses, '
+                f'not {item}'
+            )
             item = (item,)
         # Cls[item1, item2]
         else:
             assert all(
-                (child is ...) or issubclass(child, Exception) for child in item
-            ),\
-                f'{cls.__name__!r} may only be specialised by Exception subclasses'
+                (child is ...) or issubclass(child, (Exception, cls)) for child in item
+            ), (
+                f'{cls.__name__!r} may only be specialised by Exception subclasses, '
+                f'not {item}'
+            )
         return cls._get_specialisation(item)
 
     def _get_specialisation(cls, item):
@@ -182,9 +187,12 @@ class MetaConcurrent(type):
         except KeyError:
             inclusive = ... in unique_spec
             specialisations = tuple(child for child in unique_spec if child is not ...)
-            spec = ", ".join(  # the specialisation string "KeyError, IndexError, ..."
+            # the specialisation string "KeyError, IndexError, ..."
+            spec = ", ".join(
                 child.__name__ for child in specialisations
             ) + (', ...' if inclusive else '')
+            # the specialised subclass, as in
+            #
             # class 'cls.__name__[spec]'(cls, specialisations, inclusive):
             #   pass
             #
@@ -284,11 +292,11 @@ class Concurrent(BaseException, metaclass=MetaConcurrent):
     #: Basic template of specialisation
     template: ClassVar[MetaConcurrent]
     #: Exceptions that occurred concurrently
-    children: Tuple[Exception, ...]
+    children: 'Tuple[Union[Concurrent, Exception], ...]'
 
     # __new__ automatically specialises Concurrent to match its children.
     # Concurrent(A(), B()) => Concurrent[A, B](A(), B())
-    def __new__(cls: 'Type[Concurrent]', *children: Exception):
+    def __new__(cls: 'Type[Concurrent]', *children: 'Union[Concurrent, Exception]'):
         if not children:
             assert cls.specialisations is None,\
                 f"specialisation {cls.specialisations} does not match"\
@@ -297,7 +305,7 @@ class Concurrent(BaseException, metaclass=MetaConcurrent):
         special_cls = cls[tuple(type(child) for child in children)]
         return super().__new__(special_cls)
 
-    def __init__(self, *children: Exception):
+    def __init__(self, *children: 'Union[Concurrent, Exception]'):
         super().__init__(children)
         self.children = children
 
@@ -309,3 +317,25 @@ class Concurrent(BaseException, metaclass=MetaConcurrent):
         return \
             f'<object usim.{self.__class__.__name__} '\
             f'of {", ".join(map(repr, self.children))}>'
+
+    def flattened(self) -> 'Concurrent':
+        """
+        Collapse nested Concurrent exceptions
+
+        Recursively collapses nested ``Concurrent`` exceptions to provide a single
+        ``Concurrent`` exception containing all :py:attr:`~.children` of the hierarchy.
+        For example, flattening a ``Concurrent(Concurrent(KeyError()), IndexError())``
+        provides a ``Concurrent(KeyError(), IndexError())``.
+        """
+        if not any(isinstance(exc, Concurrent) for exc in self.children):
+            return self
+        leafs = []
+        for child in self.children:
+            if isinstance(child, Concurrent):
+                leafs.extend(child.flattened().children)
+            else:
+                leafs.append(child)
+        flat = Concurrent(*leafs)
+        flat.__cause__ = self.__cause__
+        flat.__context__ = self.__context__
+        return flat

Concurrent Exception Cascade is ill-defined
**Description of the bug**
A ``Concurrent`` Exception may not contain another ``Concurrent`` Exception. If one ``Scope`` failure cascades into another, this breaks error handling.

**How To Reproduce**
```python3
from usim import Scope, run

async def outer():
    async with Scope() as scope:
        scope.do(inner())

async def inner():
    async with Scope() as scope:
        scope.do(async_raise(KeyError))

async def async_raise(e):
    raise e

run(outer())
```

This fails with ``AssertionError: 'Concurrent' may only be specialised by Exception subclasses``.

**Expected behavior**
Cascading ``Concurrent`` Exceptions should be well-defined. Either allow ``Concurrent`` to contain itself, or flatten contained exceptions.
diff --git a/usim_pytest/test_types/test_concurrent_exception.py b/usim_pytest/test_types/test_concurrent_exception.py
index bbb209b..b85be77 100644
--- a/usim_pytest/test_types/test_concurrent_exception.py
+++ b/usim_pytest/test_types/test_concurrent_exception.py
@@ -10,6 +10,11 @@ class FakeConcurrent:
 
 class TestConcurrent:
     """Test the semantics of ``usim.Concurrent``"""
+    def test_misuse(self):
+        base_case = Concurrent[KeyError]
+        with pytest.raises(TypeError):
+            base_case[IndexError]
+
     def test_generic(self):
         """Generic ``Concurrent`` is a catch-all"""
         with pytest.raises(Concurrent):
@@ -126,3 +131,52 @@ class TestConcurrent:
         assert not issubclass(
             Concurrent[KeyError, LookupError], Concurrent[KeyError, RuntimeError]
         )
+
+    def test_flattened_type(self):
+        """Flattening ``Concurrent[[Concurrent[a], b, ...]`` type-checks to flat case"""
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError())).flattened()), Concurrent[KeyError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError()), KeyError()).flattened()),
+            Concurrent[KeyError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError()), IndexError()).flattened()),
+            Concurrent[KeyError, IndexError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(LookupError()), IndexError()).flattened()),
+            Concurrent[LookupError, IndexError]
+        )
+        assert issubclass(
+            type(Concurrent(KeyError()).flattened()), Concurrent[KeyError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError())).flattened().flattened()),
+            Concurrent[KeyError],
+        )
+
+    def test_unflattend_type(self):
+        """Unflattened ``Concurrent[[Concurrent[a], b, ...]`` type-checks precisely"""
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError()))), Concurrent[Concurrent[KeyError]]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError()), KeyError())),
+            Concurrent[Concurrent[KeyError], KeyError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(KeyError()), IndexError())),
+            Concurrent[Concurrent[KeyError], IndexError]
+        )
+        assert issubclass(
+            type(Concurrent(Concurrent(LookupError()), IndexError())),
+            Concurrent[Concurrent[LookupError], IndexError]
+        )
+
+    def test_flattened_value(self):
+        children = KeyError(), KeyError(), IndexError()
+        assert Concurrent(
+            Concurrent(children[0]), *children[1:]
+        ).flattened().children == children
diff --git a/usim_pytest/test_types/test_scope.py b/usim_pytest/test_types/test_scope.py
index a024ac1..58cfe58 100644
--- a/usim_pytest/test_types/test_scope.py
+++ b/usim_pytest/test_types/test_scope.py
@@ -138,6 +138,32 @@ class TestExceptions:
                     scope.do(async_raise(KeyError(), 0))
                     raise exc_type
 
+    @via_usim
+    async def test_fail_nested(self):
+        """Nested failures may be handled"""
+        async def inner_raise(exc: BaseException):
+            async with Scope() as scope:
+                scope.do(async_raise(exc))
+
+        # raising is valid
+        with pytest.raises(Concurrent):
+            async with Scope() as scope:
+                scope.do(inner_raise(KeyError()))
+        # handling is valid
+        with pytest.raises(Concurrent[Concurrent[KeyError]]):
+            async with Scope() as scope:
+                scope.do(inner_raise(KeyError()))
+        with pytest.raises(Concurrent[Concurrent]):
+            async with Scope() as scope:
+                scope.do(inner_raise(KeyError()))
+        with pytest.raises(Concurrent[Concurrent]):
+            # this layer is expected to let the exception pass through
+            try:
+                async with Scope() as scope:
+                    scope.do(inner_raise(KeyError()))
+            except Concurrent[Concurrent[IndexError]]:
+                assert False
+
 
 class TestScoping:
     @via_usim

pip install -e .[contrib]
parse_log_pytest
pytest
pytest flake8 coverage
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
coverage==7.2.7
flake8==5.0.4
flake8-bugbear==23.3.12
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mccabe==0.7.0
packaging @ file:///croot/packaging_1671697413597/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pyflakes==2.5.0
pytest==7.1.2
pytest-timeout==2.3.1
sortedcontainers==2.4.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
-e git+https://github.com/MaineKuehn/usim.git@20bf8730e26dfa3fecc095bf691b71903dcbe85c#egg=usim
zipp @ file:///croot/zipp_1672387121353/work

name: usim
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - flake8==5.0.4
      - flake8-bugbear==23.3.12
      - importlib-metadata==4.2.0
      - mccabe==0.7.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - pytest-timeout==2.3.1
      - sortedcontainers==2.4.0
prefix: /opt/conda/envs/usim

usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_flattened_type usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_unflattend_type usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_flattened_value usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_nested
usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_misuse usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_generic usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_single usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_multi usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_single_open usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_multi_open usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_base usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_identity usim_pytest/test_types/test_concurrent_exception.py::TestConcurrent::test_specialised_subclass usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_scope usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_child usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_children usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_specialisation usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_privileged usim_pytest/test_types/test_scope.py::TestScoping::test_do_after usim_pytest/test_types/test_scope.py::TestScoping::test_do_at usim_pytest/test_types/test_scope.py::TestScoping::test_do_misuse usim_pytest/test_types/test_scope.py::TestScoping::test_teardown_late usim_pytest/test_types/test_scope.py::TestScoping::test_spawn_late usim_pytest/test_types/test_scope.py::TestScoping::test_spawn_after_shutdown usim_pytest/test_types/test_scope.py::TestScoping::test_close_volatile
usim/_primitives/concurrent_exception.py docs/source/topics/exceptions.rst
usim/_primitives/concurrent_exception.py docs/source/topics/exceptions.rst