Small POC for doctest and recipes
diff --git a/burr/core/action.py b/burr/core/action.py
index a69db06..2c496af 100644
--- a/burr/core/action.py
+++ b/burr/core/action.py
@@ -1008,11 +1008,14 @@
         """Combines two conditions with an OR operator. This will return a new condition
         that is the OR of the two conditions.
 
-        To check if either foo is bar or baz is qux:
+        To check if either ``foo`` is ``"bar"`` or ``baz`` is ``"qux"``:
 
-        .. code-block:: python
-
-            condition = Condition.when(foo="bar") | Condition.when(baz="qux")
+        >>> from burr.core import Condition, State
+        >>> condition = Condition.when(foo="bar") | Condition.when(baz="qux")
+        >>> condition.resolver(State({"foo": "bar", "baz": "nope"}))
+        True
+        >>> condition.resolver(State({"foo": "nope", "baz": "nope"}))
+        False
 
         :param other: Other condition to OR with
         :return: A new condition that is the OR of the two conditions
diff --git a/docs/concepts/state.rst b/docs/concepts/state.rst
index 7442313..ba28801 100644
--- a/docs/concepts/state.rst
+++ b/docs/concepts/state.rst
@@ -32,15 +32,13 @@
 meaning that you can only create new states from old ones, not modify them in place.
 
 
-State manipulation is done through calling methods on the ``State`` class. The most common write are:
+State manipulation is done through calling methods on the ``State`` class. Because state is immutable,
+each write returns a new ``State`` object. The most common writes are:
 
-.. code-block:: python
-
-    state.update(foo=bar)  # update the state with the key "foo" set to "bar"
-    state.append(foo=bar)  # append "bar" to the list at "foo"
-    state.increment(foo=1)  # increment the value at "foo" by 1
-    state.wipe(keep=["foo", "bar"])  # remove all keys except "foo" and "bar"
-    state.wipe(delete=["foo", "bar"])  # remove "foo" and "bar" from the state
+.. literalinclude:: ../../tests/docs/recipes/state.py
+   :language: python
+   :start-after: # docs:start:manipulate-state
+   :end-before: # docs:end:manipulate-state
 
 .. warning::
 
@@ -54,10 +52,10 @@
 The read operations extend from those in the `Mapping <https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping>`_
 interface, but there are a few extra:
 
-.. code-block:: python
-
-    state.subset(["foo", "bar"])  # return a new state with only the keys "foo" and "bar"
-    state.get_all()  # return a dictionary with every key/value of the state
+.. literalinclude:: ../../tests/docs/recipes/state.py
+   :language: python
+   :start-after: # docs:start:read-state
+   :end-before: # docs:end:read-state
 
 When an update action is run, the state is first subsetted to get just the keys that are being read from,
 then the action is run, and a new state is written to. This state is merged back into the original state
diff --git a/docs/conf.py b/docs/conf.py
index e33a351..9126778 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -46,6 +46,7 @@
 extensions = [
     "sphinx.ext.autodoc",
     "sphinx.ext.autosummary",
+    "sphinx.ext.doctest",
     "myst_nb",
     "sphinx_sitemap",
     "sphinx_toolbox.collapse",
diff --git a/tests/docs/recipes/state.py b/tests/docs/recipes/state.py
new file mode 100644
index 0000000..88bd05b
--- /dev/null
+++ b/tests/docs/recipes/state.py
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# docs:start:manipulate-state
+from burr.core import State
+
+state = State({"count": 1, "messages": ["hello"], "status": "draft"})
+
+updated = state.update(status="ready")  # set the key "status" to "ready"
+appended = updated.append(messages="goodbye")  # append "goodbye" to the list at "messages"
+incremented = appended.increment(count=1)  # increment the value at "count" by 1
+
+kept = incremented.wipe(keep=["count", "messages"])  # remove all keys except these two
+deleted = incremented.wipe(delete=["status"])  # remove "status" from the state
+# docs:end:manipulate-state
+
+
+# docs:start:read-state
+message_state = incremented.subset("messages", "status")  # new state with only these keys
+all_values = incremented.get_all()  # dictionary with every key/value of the state
+# docs:end:read-state
+
+
+assert state.get_all() == {
+    "count": 1,
+    "messages": ["hello"],
+    "status": "draft",
+}
+assert incremented.get_all() == {
+    "count": 2,
+    "messages": ["hello", "goodbye"],
+    "status": "ready",
+}
+assert kept.get_all() == {"count": 2, "messages": ["hello", "goodbye"]}
+assert deleted.get_all() == kept.get_all()
+assert message_state.get_all() == {
+    "messages": ["hello", "goodbye"],
+    "status": "ready",
+}
+assert all_values == incremented.get_all()
diff --git a/tests/docs/test_recipes.py b/tests/docs/test_recipes.py
new file mode 100644
index 0000000..87cc735
--- /dev/null
+++ b/tests/docs/test_recipes.py
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+RECIPES_DIR = Path(__file__).parent / "recipes"
+REPOSITORY_ROOT = Path(__file__).parents[2]
+
+
+@pytest.mark.parametrize("recipe", sorted(RECIPES_DIR.glob("*.py")), ids=lambda path: path.stem)
+def test_recipe_runs_in_isolation(recipe: Path, tmp_path: Path) -> None:
+    env = os.environ.copy()
+    python_path = env.get("PYTHONPATH")
+    env["PYTHONPATH"] = os.pathsep.join(
+        part for part in (str(REPOSITORY_ROOT), python_path) if part
+    )
+
+    result = subprocess.run(
+        [sys.executable, str(recipe)],
+        cwd=tmp_path,
+        env=env,
+        text=True,
+        capture_output=True,
+        check=False,
+    )
+
+    assert result.returncode == 0, result.stdout + result.stderr