Repository: griptape-ai/griptape (language: python)

Issue:
Add support for branching in `Workflow`s
 - [x] I have read and agree to the [contributing guidelines](https://github.com/griptape-ai/griptape#contributing).

**Is your feature request related to a problem? Please describe.**
I am trying to build a workflow where I want to conditionally run different `Task`s based on the parent's output.

**Describe the solution you'd like**
I want to be able to conditionally branch to different `Task`s depending on the output of the parent.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a4655652..33824f753 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - `VectorStoreClient.query_params` dict for custom query params.
   - `VectorStoreClient.process_query_output_fn` for custom query output processing logic.
 - Parameter `fail_fast` to `Structure`.
+- `BooleanArtifact` for handling boolean values.
 
 ### Changed
 - **BREAKING**: `BaseVectorStoreDriver.upsert_text_artifact()` and `BaseVectorStoreDriver.upsert_text()` use artifact/string values to generate `vector_id` if it wasn't implicitly passed. This change ensures that we don't generate embeddings for the same content every time.
diff --git a/docs/griptape-framework/data/artifacts.md b/docs/griptape-framework/data/artifacts.md
index 5b69b120a..6fe77574b 100644
--- a/docs/griptape-framework/data/artifacts.md
+++ b/docs/griptape-framework/data/artifacts.md
@@ -1,11 +1,11 @@
 ## Overview
 
-**Artifacts** are used for passing different types of data between Griptape components. All tools return artifacts that are later consumed by tasks and task memory. 
+**[Artifacts](../../reference/griptape/artifacts/base_artifact.md)** are used for passing different types of data between Griptape components. All tools return artifacts that are later consumed by tasks and task memory. 
 Artifacts make sure framework components enforce contracts when passing and consuming data.
 
 ## TextArtifact
 
-Used for passing text data of arbitrary size around the framework. It can be used to count tokens with [token_count()](../../reference/griptape/artifacts/text_artifact.md#griptape.artifacts.text_artifact.TextArtifact.token_count) with a tokenizer. 
+A [TextArtifact](../../reference/griptape/artifacts/text_artifact.md) for passing text data of arbitrary size around the framework. It can be used to count tokens with [token_count()](../../reference/griptape/artifacts/text_artifact.md#griptape.artifacts.text_artifact.TextArtifact.token_count) with a tokenizer. 
 It can also be used to generate a text embedding with [generate_embedding()](../../reference/griptape/artifacts/text_artifact.md#griptape.artifacts.text_artifact.TextArtifact.generate_embedding) 
 and access it with [embedding](../../reference/griptape/artifacts/text_artifact.md#griptape.artifacts.text_artifact.TextArtifact.embedding).
 
@@ -13,20 +13,20 @@ and access it with [embedding](../../reference/griptape/artifacts/text_artifact.
 
 ## CsvRowArtifact
 
-Used for passing structured row data around the framework. It inherits from [TextArtifact](../../reference/griptape/artifacts/text_artifact.md) and overrides the 
+A [CsvRowArtifact](../../reference/griptape/artifacts/csv_row_artifact.md) for passing structured row data around the framework. It inherits from [TextArtifact](../../reference/griptape/artifacts/text_artifact.md) and overrides the 
 [to_text()](../../reference/griptape/artifacts/csv_row_artifact.md#griptape.artifacts.csv_row_artifact.CsvRowArtifact.to_text) method, which always returns a valid CSV row.
 
 ## InfoArtifact
 
-Used for passing short notifications back to the LLM without task memory storing them.
+An [InfoArtifact](../../reference/griptape/artifacts/info_artifact.md) for passing short notifications back to the LLM without task memory storing them.
 
 ## ErrorArtifact
 
-Used for passing errors back to the LLM without task memory storing them.
+An [ErrorArtifact](../../reference/griptape/artifacts/error_artifact.md) is used for passing errors back to the LLM without task memory storing them.
 
 ## BlobArtifact
 
-Used for passing binary large objects (blobs) back to the LLM. 
+A [BlobArtifact](../../reference/griptape/artifacts/blob_artifact.md) for passing binary large objects (blobs) back to the LLM. 
 Treat it as a way to return unstructured data, such as images, videos, audio, and other files back from tools. 
 Each blob has a [name](../../reference/griptape/artifacts/base_artifact.md#griptape.artifacts.base_artifact.BaseArtifact.name) and 
 [dir](../../reference/griptape/artifacts/blob_artifact.md#griptape.artifacts.blob_artifact.BlobArtifact.dir_name) to uniquely identify stored objects.
@@ -40,3 +40,10 @@ An [ImageArtifact](../../reference/griptape/artifacts/image_artifact.md) is used
 ## AudioArtifact
 
 An [AudioArtifact](../../reference/griptape/artifacts/audio_artifact.md) allows the Framework to interact with audio content. An Audio Artifact includes binary audio content as well as metadata like format, duration, and prompt and model information for audio returned generative models. It inherits from [BlobArtifact](#blobartifact).
+
+## BooleanArtifact
+
+A [BooleanArtifact](../../reference/griptape/artifacts/boolean_artifact.md) is used for passing boolean values around the framework.
+
+!!! info
+    Any object passed on init to `BooleanArtifact` will be coerced into a `bool` type. This might lead to unintended behavior: `BooleanArtifact("False").value is True`. Use [BooleanArtifact.parse_bool](../../reference/griptape/artifacts/boolean_artifact.md#griptape.artifacts.boolean_artifact.BooleanArtifact.parse_bool) to convert case-insensitive string literal values `"True"` and `"False"` into a `BooleanArtifact`: `BooleanArtifact.parse_bool("False").value is False`.
diff --git a/griptape/artifacts/__init__.py b/griptape/artifacts/__init__.py
index e57177ac4..00c19519f 100644
--- a/griptape/artifacts/__init__.py
+++ b/griptape/artifacts/__init__.py
@@ -3,6 +3,7 @@
 from .info_artifact import InfoArtifact
 from .text_artifact import TextArtifact
 from .blob_artifact import BlobArtifact
+from .boolean_artifact import BooleanArtifact
 from .csv_row_artifact import CsvRowArtifact
 from .list_artifact import ListArtifact
 from .media_artifact import MediaArtifact
@@ -16,6 +17,7 @@
     "InfoArtifact",
     "TextArtifact",
     "BlobArtifact",
+    "BooleanArtifact",
     "CsvRowArtifact",
     "ListArtifact",
     "MediaArtifact",
diff --git a/griptape/artifacts/boolean_artifact.py b/griptape/artifacts/boolean_artifact.py
new file mode 100644
index 000000000..a7a84da18
--- /dev/null
+++ b/griptape/artifacts/boolean_artifact.py
@@ -0,0 +1,31 @@
+from __future__ import annotations
+from typing import Any, Union
+from attrs import define, field
+from griptape.artifacts import BaseArtifact
+
+
+@define
+class BooleanArtifact(BaseArtifact):
+    value: bool = field(converter=bool, metadata={"serializable": True})
+    meta: dict[str, Any] = field(factory=dict, kw_only=True, metadata={"serializable": True})
+
+    @classmethod
+    def parse_bool(cls, value: Union[str, bool]) -> BooleanArtifact:
+        """
+        Convert a string literal or bool to a BooleanArtifact. The string must be either "true" or "false" with any casing.
+        """
+        if value is not None:
+            if isinstance(value, str):
+                if value.lower() == "true":
+                    return BooleanArtifact(True)
+                elif value.lower() == "false":
+                    return BooleanArtifact(False)
+            elif isinstance(value, bool):
+                return BooleanArtifact(value)
+        raise ValueError(f"Cannot convert '{value}' to BooleanArtifact")
+
+    def __add__(self, other: BaseArtifact) -> BooleanArtifact:
+        raise ValueError("Cannot add BooleanArtifact with other artifacts")
+
+    def __eq__(self, value: object) -> bool:
+        return self.value is value


Test patch:
diff --git a/tests/unit/artifacts/test_boolean_artifact.py b/tests/unit/artifacts/test_boolean_artifact.py
new file mode 100644
index 000000000..bcad67673
--- /dev/null
+++ b/tests/unit/artifacts/test_boolean_artifact.py
@@ -0,0 +1,35 @@
+import pytest
+from griptape.artifacts import BooleanArtifact
+
+
+class TestBooleanArtifact:
+    def test_parse_bool(self):
+        assert BooleanArtifact.parse_bool("true").value is True
+        assert BooleanArtifact.parse_bool("false").value is False
+        assert BooleanArtifact.parse_bool("True").value is True
+        assert BooleanArtifact.parse_bool("False").value is False
+
+        with pytest.raises(ValueError):
+            BooleanArtifact.parse_bool("foo")
+
+        with pytest.raises(ValueError):
+            BooleanArtifact.parse_bool(None)
+
+        assert BooleanArtifact.parse_bool(True).value is True
+        assert BooleanArtifact.parse_bool(False).value is False
+
+    def test_add(self):
+        with pytest.raises(ValueError):
+            BooleanArtifact(True) + BooleanArtifact(True)
+
+    def test_value_type_conversion(self):
+        assert BooleanArtifact(1).value is True
+        assert BooleanArtifact(0).value is False
+        assert BooleanArtifact(True).value is True
+        assert BooleanArtifact(False).value is False
+        assert BooleanArtifact("true").value is True
+        assert BooleanArtifact("false").value is True
+        assert BooleanArtifact([1]).value is True
+        assert BooleanArtifact([]).value is False
+        assert BooleanArtifact(False) == False
+        assert BooleanArtifact(True) == True