diff --git a/shared/workspaces/workspaces/capability/enums.py b/shared/workspaces/workspaces/capability/enums.py
index 7244de245d7abbcdb699f3a4056313524d6d709b..35427ed2519d39543dcaa3a3924544998e5b127c 100644
--- a/shared/workspaces/workspaces/capability/enums.py
+++ b/shared/workspaces/workspaces/capability/enums.py
@@ -14,6 +14,7 @@ class CapabilityStepType(Enum):
     AwaitProduct = 3
     AwaitParameter = 4
     AwaitLargeAllocApproval = 5
+    Invalid = -1
 
     @classmethod
     def from_string(cls, string: str) -> CapabilityStepType:
@@ -31,7 +32,7 @@ class CapabilityStepType(Enum):
             "await-parameter": cls.AwaitParameter,
             "await-large-alloc-approval": cls.AwaitLargeAllocApproval,
         }
-        return strings[string]
+        return strings.get(string, cls.Invalid)
 
 
 class CapabilityEventType(Enum):
diff --git a/shared/workspaces/workspaces/capability/helpers.py b/shared/workspaces/workspaces/capability/helpers.py
index 13b42bc83259c5145d4715ed57b32e34d5c57755..24c71836e4ab9f296dbf369ffbf7fc4940433ddb 100644
--- a/shared/workspaces/workspaces/capability/helpers.py
+++ b/shared/workspaces/workspaces/capability/helpers.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import json
+import re
 from typing import Iterator, List, Optional
 
 from workspaces.capability.enums import CapabilityStepType
@@ -13,6 +14,10 @@ from workspaces.capability.schema_interfaces import CapabilityExecutionIF
 from workspaces.capability.services.interfaces import CapabilityEngineIF
 
 
+class MalformedCapabilityStep(ValueError):
+    pass
+
+
 class CapabilityStep(CapabilityStepIF):
     """
     Class that represents a step in a capability sequence. A step is of a certain type and
@@ -31,25 +36,63 @@ class CapabilityStep(CapabilityStepIF):
         self.step_value = step_value
         self.step_args = step_args
 
+    @staticmethod
+    def parse_args(args_string: str) -> Optional[List[str]]:
+        """
+        Parse a string of the form [arg-1, arg_2, -a 3], etc. into a list of those args
+
+        :param args_string: Args as a string
+        :return: List of given args or None if no args given
+        """
+        args_string = args_string.replace("[", "").replace("]", "")
+        return args_string.split(", ") if args_string else None
+
     @classmethod
-    def from_str(cls, step_string: str):
+    def from_str(cls, step_string: str) -> CapabilityStep:
         """
         Create CapabilityStep from string containing space-separated type, value, and args
 
         :param step_string: String of capability step, e.g. "PrepareAndRunWorkflow null"
         :return: CapabilityStep of given string
         """
-        step_list = step_string.split(" ")
-        step_type = CapabilityStepType.from_string(step_list[0])
-        if step_type is CapabilityStepType.PrepareAndRunWorkflow:
-            step_value = step_list[1]
-            step_args = step_list[2] if len(step_list) == 3 else None
+        # === Regexes ===
+        # Matches a string with letters, digits, underscores, and hyphens, labeled steptype
+        # Ex: prepare-and-run-workflow, await-qa
+        r_step_type = r"(?P<steptype>[\w-]+)"
+        # Matches a string with letters, digits, underscores, and these specials: [./:], labeled stepval
+        # Ex: cal://alma/..., workflow_name, null
+        r_step_val = r"(?P<stepval>[\w\-./:]+)"
+        # Matches a string surrounded by [], with 0 or more of the same string as above (plus spaces), separated by
+        # ", " inside them
+        # Ex: [-f, -l path/to/location, arg_value], [arg], []
+        r_step_args = r"(?P<stepargs>\[(?:[\w\-./: ]+(?:, )?)*\])"
+        # Matches a string taking the form of a full capability step
+        # Ex: prepare-and-run-workflow null [-g], prepare-and-run-workflow download, await-qa
+        r_step = rf"^{r_step_type} ?{r_step_val}? ?{r_step_args}?$"
+
+        if match := re.search(r_step, step_string):
+            groups = match.groupdict()
+            step_type = groups.get("steptype", None)
+            step_value = groups.get("stepval", None)
+            step_args = groups.get("stepargs", None)
+            if step_type:
+                # Translate step_type from str to CapabilityStepType
+                step_type = CapabilityStepType.from_string(step_type)
+
+                if step_type is CapabilityStepType.Invalid:
+                    raise MalformedCapabilityStep(f"Step type {step_type} invalid.")
+            else:
+                raise MalformedCapabilityStep(
+                    f"Capability step {step_string} is malformed. Step type not found when it is required."
+                )
+            if step_args:
+                step_args = cls.parse_args(step_args)
+            return cls.TYPES[step_type](step_type, step_value, step_args)
         else:
-            # Any other step type
-            step_value = step_list[1] if len(step_list) == 2 else None
-            step_args = None
-
-        return cls.TYPES[step_type](step_type, step_value, step_args)
+            # Step string not well-formatted
+            raise MalformedCapabilityStep(
+                f"Capability step {step_string} is malformed."
+            )
 
     def __str__(self):
         string = f"{self.step_type.name}"
@@ -122,7 +165,9 @@ class PrepareAndRunWorkflow(CapabilityStep):
             # DO NOT TAKE THIS OUT! Python will yell at you.
             parameters = json.dumps(parameters)
             workflow_args = json.loads(parameters)
-        engine.submit_workflow_request(execution.id, workflow_name, workflow_args, files)
+        engine.submit_workflow_request(
+            execution.id, workflow_name, workflow_args, files
+        )
 
 
 class AwaitQa(CapabilityStep):
diff --git a/shared/workspaces/workspaces/capability/test/test_helpers.py b/shared/workspaces/workspaces/capability/test/test_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..38aafc6f267bf300989d3184c79335842b3ab134
--- /dev/null
+++ b/shared/workspaces/workspaces/capability/test/test_helpers.py
@@ -0,0 +1,68 @@
+import pytest
+
+from workspaces.capability.enums import CapabilityStepType
+from workspaces.capability.helpers import CapabilityStep, MalformedCapabilityStep
+
+
+def test_capability_step_from_str():
+    """
+    Tests that a capability step can be correctly parsed from a string
+    """
+    step_with_val_and_args = "prepare-and-run-workflow deliver [-r, -l ., shared/workspaces/test/test_data/spool/724126739/17A-109.sb33151331.eb33786546.57892.65940042824]"
+    step_with_val_and_one_arg = "prepare-and-run-workflow null [-g]"
+    step_with_val_and_empty_args = "prepare-and-run-workflow workflow []"
+    step_with_val = "prepare-and-run-workflow name_of_workflow"
+    step_only_type = "await-qa"
+
+    # Step of the form "step-type step-value [step_arg1, ...]"
+    step_with_val_and_args = CapabilityStep.from_str(step_with_val_and_args)
+    assert step_with_val_and_args.step_type is CapabilityStepType.PrepareAndRunWorkflow
+    assert step_with_val_and_args.step_value == "deliver"
+    assert step_with_val_and_args.step_args == [
+        "-r",
+        "-l .",
+        "shared/workspaces/test/test_data/spool/724126739/17A-109.sb33151331.eb33786546.57892.65940042824",
+    ]
+
+    # Step of the form "step-type step-value [step_arg]"
+    step_with_val_and_one_arg = CapabilityStep.from_str(step_with_val_and_one_arg)
+    assert (
+        step_with_val_and_one_arg.step_type is CapabilityStepType.PrepareAndRunWorkflow
+    )
+    assert step_with_val_and_one_arg.step_value == "null"
+    assert step_with_val_and_one_arg.step_args == ["-g"]
+
+    # Step of the form "step-type step-value []"
+    step_with_val_and_empty_args = CapabilityStep.from_str(step_with_val_and_empty_args)
+    assert (
+        step_with_val_and_empty_args.step_type
+        is CapabilityStepType.PrepareAndRunWorkflow
+    )
+    assert step_with_val_and_empty_args.step_value == "workflow"
+    assert step_with_val_and_empty_args.step_args is None
+
+    # Step of the form "step-type step-value"
+    step_with_val = CapabilityStep.from_str(step_with_val)
+    assert step_with_val.step_type is CapabilityStepType.PrepareAndRunWorkflow
+    assert step_with_val.step_value == "name_of_workflow"
+    assert step_with_val.step_args is None
+
+    # Step of the form "step-type"
+    step_only_type = CapabilityStep.from_str(step_only_type)
+    assert step_only_type.step_type is CapabilityStepType.AwaitQA
+    assert step_only_type.step_value is None
+    assert step_only_type.step_args is None
+
+
+def test_capability_step_from_str_error():
+    """
+    Tests that an invalid capability step strings will correctly throw an error
+    """
+    step_empty = ""
+    step_bad_format = "!step.type step$value arg1 arg2 arg3"
+    step_invalid_type = "invalid-step-type step-value [arg1, arg2]"
+
+    with pytest.raises(MalformedCapabilityStep):
+        CapabilityStep.from_str(step_empty)
+        CapabilityStep.from_str(step_bad_format)
+        CapabilityStep.from_str(step_invalid_type)