diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/common.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/common.py
index c60f6948ded..cb081d5c003 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/common.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/common.py
@@ -1,8 +1,93 @@
 """Common flex stacker base models."""
-from typing import Literal
 
-from ...errors import ErrorOccurrence
+from __future__ import annotations
+from dataclasses import dataclass
+from typing import Literal, TYPE_CHECKING, Sequence, Iterator
+from textwrap import dedent
+
 from opentrons_shared_data.errors import ErrorCodes
+from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
+from opentrons_shared_data.labware.labware_definition import LabwareDefinition
+
+
+from ...errors import ErrorOccurrence
+from ...types import (
+    StackerStoredLabwareGroup,
+    InStackerHopperLocation,
+    LoadedLabware,
+    OFF_DECK_LOCATION,
+    OnLabwareLocation,
+    OnLabwareLocationSequenceComponent,
+    LabwareLocationSequence,
+    LabwareLocation,
+    LabwareOffsetLocationSequence,
+    OnLabwareOffsetLocationSequenceComponent,
+    ModuleLocation,
+)
+from ...state.update_types import StateUpdate
+
+
+if TYPE_CHECKING:
+    from opentrons.protocol_engine.execution import EquipmentHandler
+    from opentrons.protocol_engine.state.state import StateView
+    from opentrons.protocol_engine.resources import ModelUtils
+    from opentrons.protocol_engine.execution.equipment import LoadedLabwarePoolData
+    from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
+
+INITIAL_COUNT_DESCRIPTION = dedent(
+    """\
+        The number of labware that should be initially stored in the stacker. This number will be silently clamped to
+        the maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker.
+
+        This field works with the initialStoredLabware field in a complex way.
+
+        The following must be true for initialCount to be valid:
+          - It is not specified, and initialStoredLabware is not specified, in which case the stacker will start empty
+          - It is not specified, and initialStoredLabware is specified, in which case the contents of the stacker are entirely
+            determined by initialStoredLabware.
+          - It is specified, and initialStoredLabware is specified, in which case the length of initialStoredLabware must be
+            exactly initialCount, and the contents of the stacker will be determined by initialStoredLabware.
+        """
+)
+
+INITIAL_STORED_LABWARE_DESCRIPTION = dedent(
+    """\
+        A list of IDs that should be initially stored in the stacker.
+
+        If specified, the first element of the list is the labware on the physical bottom that will be the first labware retrieved.
+
+        This is a complex field. The following must be true for the field to be valid:
+        - If this field is specified, then either initialCount must not be specified, or this field must have exactly initalCount elements
+        - Each element must contain an id for each corresponding labware details field (i.e. if lidLabware is specified, each element must have
+          a lidLabwareId) and must not contain an id for a corresponding labware details field that is not specified (i.e., if adapterLabware
+          is not specified, each element must not have an adapterLabwareId).
+
+        The behavior of the command depends on the values of both this field and initialCount.
+        - If this field is not specified and initialCount is not specified, the command will create the maximum number of labware objects
+          the stacker can hold according to the labware pool specifications.
+        - If this field is not specified and initialCount is specified to be 0, the command will create 0 labware objects and the stacker will be empty.
+        - If this field is not specified and initialCount is specified to be non-0, the command will create initialCount labware objects of
+          each specified labware type (primary, lid, and adapter), with appropriate positions, and arbitrary IDs, loaded into the stacker
+        - If this field is specified (and therefore initialCount is not specified or is specified to be the length of this field) then the
+          command will create labware objects with the IDs specified in this field and appropriate positions, loaded into the stacker.
+
+        Behavior is also different depending on whether the labware identified by ID in this field exist or not. Either all labware specified
+        in this field must exist, or all must not exist.
+
+        Further,
+        - If the labware exist, they must be of the same type as identified in the primaryLabware field.
+        - If the labware exist and the adapterLabware field is specified, each labware must be currently loaded on a labware of the same kind as
+          specified in the adapterLabware field, and that labware must be loaded off-deck
+        - If the labware exist and the adapterLabware field is not specified, each labware must be currently loaded off deck directly
+        - If the labware exist and the lidLabware field is specified, each labware must currently have a loaded lid of the same kind as specified
+          in the lidLabware field
+        - If the labware exist and the lidLabware field is not specified, each labware must not currently have a lid
+        - If the labware exist, they must have nothing loaded underneath them or above them other than what is mentioned above
+
+        If all the above are true, when this command executes the labware will be immediately moved into InStackerHopper. If any of the above
+        are not true, analysis will fail.
+        """
+)
 
 
 class FlexStackerStallOrCollisionError(ErrorOccurrence):
@@ -35,3 +120,737 @@ class FlexStackerHopperError(ErrorOccurrence):
 
     errorCode: str = ErrorCodes.STACKER_HOPPER_LABWARE_FAILED.value.code
     detail: str = ErrorCodes.STACKER_HOPPER_LABWARE_FAILED.value.detail
+
+
+@dataclass
+class _LabwareDefPair:
+    definition: LabwareDefinition
+    id: str
+
+
+@dataclass
+class _GroupWithDefs:
+    primary: _LabwareDefPair
+    adapter: _LabwareDefPair | None
+    lid: _LabwareDefPair | None
+
+
+@dataclass
+class LabwareWithLocationSequence:
+    """Holds a labware id and location."""
+
+    labwareId: str
+    locationSequence: LabwareLocationSequence
+
+
+@dataclass
+class GroupWithLocationSequences:
+    """Holds labware id and location for group components."""
+
+    primary: LabwareWithLocationSequence
+    adapter: LabwareWithLocationSequence | None
+    lid: LabwareWithLocationSequence | None
+
+
+def _labware_location_seq_for_primary(
+    group: StackerStoredLabwareGroup,
+    base: LabwareLocationSequence,
+) -> LabwareWithLocationSequence:
+    if group.adapterLabwareId is not None:
+        return LabwareWithLocationSequence(
+            labwareId=group.primaryLabwareId,
+            locationSequence=(
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId=group.adapterLabwareId, lidId=None
+                    )
+                ]
+                + base
+            ),
+        )
+    else:
+        return LabwareWithLocationSequence(
+            labwareId=group.primaryLabwareId,
+            locationSequence=base,
+        )
+
+
+def _labware_location_seq_for_lid(
+    group: StackerStoredLabwareGroup,
+    base: LabwareLocationSequence,
+) -> LabwareWithLocationSequence | None:
+    if group.lidLabwareId is None:
+        return None
+    elif group.adapterLabwareId is None:
+        return LabwareWithLocationSequence(
+            labwareId=group.lidLabwareId,
+            locationSequence=(
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId=group.primaryLabwareId, lidId=group.lidLabwareId
+                    )
+                ]
+                + base
+            ),
+        )
+    else:
+        return LabwareWithLocationSequence(
+            labwareId=group.lidLabwareId,
+            locationSequence=(
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId=group.primaryLabwareId, lidId=group.lidLabwareId
+                    ),
+                    OnLabwareLocationSequenceComponent(
+                        labwareId=group.adapterLabwareId, lidId=None
+                    ),
+                ]
+                + base
+            ),
+        )
+
+
+def _labware_location_seq_for_adapter(
+    group: StackerStoredLabwareGroup,
+    base: LabwareLocationSequence,
+) -> LabwareWithLocationSequence | None:
+    if group.adapterLabwareId is None:
+        return None
+    return LabwareWithLocationSequence(
+        labwareId=group.adapterLabwareId, locationSequence=base
+    )
+
+
+def labware_locations_for_group(
+    group: StackerStoredLabwareGroup, base: LabwareLocationSequence
+) -> GroupWithLocationSequences:
+    """Get the labware and location sequences bound together."""
+    return GroupWithLocationSequences(
+        primary=_labware_location_seq_for_primary(group, base),
+        adapter=_labware_location_seq_for_adapter(group, base),
+        lid=_labware_location_seq_for_lid(group, base),
+    )
+
+
+def labware_location_base_sequence(
+    sample_group: StackerStoredLabwareGroup,
+    state_view: StateView,
+    default: LabwareLocationSequence,
+) -> LabwareLocationSequence:
+    """Get the base location sequence for a labware group, including loading the current location if it exists."""
+    first = (
+        sample_group.adapterLabwareId
+        if sample_group.adapterLabwareId is not None
+        else sample_group.primaryLabwareId
+    )
+    if state_view.labware.known(first):
+        return state_view.geometry.get_location_sequence(first)
+    return default
+
+
+def primary_location_sequences(
+    groups: list[GroupWithLocationSequences],
+) -> list[LabwareLocationSequence]:
+    """Collate primary location sequences from lists of labware-plus-location."""
+    return [primary_location_sequence(group) for group in groups]
+
+
+def primary_location_sequence(
+    group: GroupWithLocationSequences,
+) -> LabwareLocationSequence:
+    """Get the location sequence for the primary labware."""
+    return group.primary.locationSequence
+
+
+def adapter_location_sequences(
+    groups: list[GroupWithLocationSequences],
+) -> list[LabwareLocationSequence] | None:
+    """Collate adapter location sequences from lists of labware-plus-location."""
+
+    def _yield_adapters(
+        groups: list[GroupWithLocationSequences],
+    ) -> Iterator[LabwareLocationSequence]:
+        for group in groups:
+            seq = adapter_location_sequence(group)
+            if seq is None:
+                continue
+            else:
+                yield seq
+
+    adapter_seqs = list(_yield_adapters(groups))
+    if len(adapter_seqs) != len(groups):
+        return None
+    return adapter_seqs
+
+
+def adapter_location_sequences_with_default(
+    groups: list[GroupWithLocationSequences], adapter_def: LabwareDefinition | None
+) -> list[LabwareLocationSequence] | None:
+    """Collate adapter location sequences unless there is no adapter."""
+    if adapter_def is None:
+        return None
+    else:
+        return adapter_location_sequences(groups)
+
+
+def adapter_location_sequence(
+    group: GroupWithLocationSequences,
+) -> LabwareLocationSequence | None:
+    """Get the adapter location sequence from a group."""
+    if group.adapter is None:
+        return None
+    return group.adapter.locationSequence
+
+
+def lid_location_sequences(
+    groups: list[GroupWithLocationSequences],
+) -> list[LabwareLocationSequence] | None:
+    """Collate lid location sequences from lists of labware-plus-location."""
+
+    def _yield_lids(
+        groups: list[GroupWithLocationSequences],
+    ) -> Iterator[LabwareLocationSequence]:
+        for group in groups:
+            seq = lid_location_sequence(group)
+            if seq is None:
+                continue
+            else:
+                yield seq
+
+    lid_seqs = list(_yield_lids(groups))
+    if len(lid_seqs) != len(groups):
+        return None
+    return lid_seqs
+
+
+def lid_location_sequences_with_default(
+    groups: list[GroupWithLocationSequences], lid_def: LabwareDefinition | None
+) -> list[LabwareLocationSequence] | None:
+    """Collate lid location sequences unless there is no lid."""
+    if lid_def is None:
+        return None
+    else:
+        return lid_location_sequences(groups)
+
+
+def lid_location_sequence(
+    group: GroupWithLocationSequences,
+) -> LabwareLocationSequence | None:
+    """Get the lid location sequence from a group."""
+    if group.lid is None:
+        return None
+    return group.lid.locationSequence
+
+
+def _check_one_preloaded_labware_known(
+    group: StackerStoredLabwareGroup, state_view: StateView
+) -> bool:
+    """Slightly tricky way to check if a labware group represents known labware.
+
+    Return true if all specified labware are known; false if all specified labware are not known; and
+    raise CommandPreconditionViolated if some specified labware are known and some are not.
+    """
+    if state_view.labware.known(group.primaryLabwareId):
+        if group.lidLabwareId is not None and not state_view.labware.known(
+            group.lidLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Either all labware ids must be known or none must be, but primary and lid do not match"
+            )
+        if group.adapterLabwareId is not None and not state_view.labware.known(
+            group.adapterLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Either all labware ids must be known or none must be, but primary and adapter do not match"
+            )
+
+        return True
+    else:
+        if group.lidLabwareId is not None and state_view.labware.known(
+            group.lidLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Either all labware ids must be known or none must be, but primary and lid do not match"
+            )
+        if group.adapterLabwareId is not None and state_view.labware.known(
+            group.adapterLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Either all labware ids must be known or none must be, but primary and lid do not match"
+            )
+        return False
+
+
+def _check_one_preloaded_labware(  # noqa: C901
+    pool_primary_definition: LabwareDefinition,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_lid_definition: LabwareDefinition | None,
+    group: StackerStoredLabwareGroup,
+    state_view: StateView,
+) -> None:
+    """Do some preflight checks for labware known to be preloaded.
+
+    Check that labware known to the engine is located appropriately to be loaded to the stacker.
+    hopper directly (i.e. during setStoredLabware or fill, NOT with store). While we're at it, bind the def and the id.
+    """
+    pool_primary_uri = state_view.labware.get_uri_from_definition(
+        pool_primary_definition
+    )
+    stored_primary_uri = state_view.labware.get_definition_uri(group.primaryLabwareId)
+
+    if pool_primary_uri != stored_primary_uri:
+        raise CommandPreconditionViolated(
+            f"URI {stored_primary_uri} of primary labware {group.primaryLabwareId} must match pool URI {pool_primary_uri} but does not"
+        )
+    if pool_adapter_definition:
+        if group.adapterLabwareId is None:
+            raise CommandPreconditionViolated(
+                "All pool components must have an ID, but adapter has no id"
+            )
+        stored_adapter_uri = state_view.labware.get_definition_uri(
+            group.adapterLabwareId
+        )
+        pool_adapter_uri = state_view.labware.get_uri_from_definition(
+            pool_adapter_definition
+        )
+        if stored_adapter_uri != pool_adapter_uri:
+            raise CommandPreconditionViolated(
+                f"URI {stored_adapter_uri} of adapter labware {group.adapterLabwareId} must match pool URI {pool_adapter_uri} but does not"
+            )
+        if state_view.labware.get_location(group.adapterLabwareId) != OFF_DECK_LOCATION:
+            raise CommandPreconditionViolated(
+                "All existing adapters to be loaded into a stacker must be currently OFF_DECK"
+            )
+        if state_view.labware.get_location(group.primaryLabwareId) != OnLabwareLocation(
+            labwareId=group.adapterLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Existing labware groups to be loaded into a stacker must already be associated"
+            )
+    else:
+        if group.adapterLabwareId is not None:
+            raise CommandPreconditionViolated(
+                "No unspecified pool component may have an ID, but adapter has an id"
+            )
+        if state_view.labware.get_location(group.primaryLabwareId) != OFF_DECK_LOCATION:
+            raise CommandPreconditionViolated(
+                "All existing labware without adapters to be loaded into a stacker must be currently OFF_DECK"
+            )
+    if pool_lid_definition:
+        if group.lidLabwareId is None:
+            raise CommandPreconditionViolated(
+                "All pool components must have an ID but lid has no id"
+            )
+        stored_lid_uri = state_view.labware.get_definition_uri(group.lidLabwareId)
+        pool_lid_uri = state_view.labware.get_uri_from_definition(pool_lid_definition)
+        if stored_lid_uri != pool_lid_uri:
+            raise CommandPreconditionViolated(
+                f"URI {stored_lid_uri} of lid labware {group.lidLabwareId} must match pool URI {pool_lid_uri} but does not"
+            )
+
+        if (
+            state_view.labware.get_location(group.lidLabwareId)
+            != OnLabwareLocation(labwareId=group.primaryLabwareId)
+            or state_view.labware.get_lid_id_by_labware_id(group.primaryLabwareId)
+            != group.lidLabwareId
+        ):
+            raise CommandPreconditionViolated(
+                "Existing labware groups to be loaded into a stacker must already be associated"
+            )
+    else:
+        if group.lidLabwareId is not None:
+            raise CommandPreconditionViolated(
+                "No unspecified pool component may have an id, but lid has an id"
+            )
+
+
+def check_preloaded_labware(
+    pool_primary_definition: LabwareDefinition,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_lid_definition: LabwareDefinition | None,
+    ids: list[StackerStoredLabwareGroup],
+    state_view: StateView,
+) -> None:
+    """Check whether a list of known-to-be-preloaded labware match the pool constraints."""
+    for group in ids:
+        _check_one_preloaded_labware(
+            pool_primary_definition,
+            pool_adapter_definition,
+            pool_lid_definition,
+            group,
+            state_view,
+        )
+
+
+def check_if_labware_preloaded(
+    ids: list[StackerStoredLabwareGroup], state_view: StateView
+) -> bool:
+    """Determine whether the list of ids has already been loaded or needs to be loaded."""
+    if len(ids) == 0:
+        return False
+    first = _check_one_preloaded_labware_known(ids[0], state_view)
+    for group in ids[1:]:
+        if _check_one_preloaded_labware_known(group, state_view) != first:
+            raise CommandPreconditionViolated(
+                "All labware must be previously loaded or none must be previously loaded."
+            )
+    return first
+
+
+def _add_labware_details_to_dicts(
+    definitions_by_id: dict[str, LabwareDefinition],
+    display_names_by_id: dict[str, str | None],
+    new_locations_by_id: dict[str, LabwareLocation],
+    offset_ids_by_id: dict[str, str | None],
+    definition: LabwareDefinition,
+    labware: LoadedLabware,
+) -> None:
+    definitions_by_id[labware.id] = definition
+    display_names_by_id[labware.id] = None
+    new_locations_by_id[labware.id] = labware.location
+    offset_ids_by_id[labware.id] = labware.offsetId
+
+
+def _add_pool_labware_details_to_dicts(
+    definitions_by_id: dict[str, LabwareDefinition],
+    display_names_by_id: dict[str, str | None],
+    new_locations_by_id: dict[str, LabwareLocation],
+    offset_ids_by_id: dict[str, str | None],
+    lid_parent_ids: list[str],
+    lid_ids: list[str],
+    pool_primary_definition: LabwareDefinition,
+    pool_lid_definition: LabwareDefinition | None,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_group: LoadedLabwarePoolData,
+) -> None:
+    if pool_group.lid_labware:
+        assert pool_lid_definition  # safe: only have lids if the pool has lids
+        lid_parent_ids.append(pool_group.primary_labware.id)
+        lid_ids.append(pool_group.lid_labware.id)
+        _add_labware_details_to_dicts(
+            definitions_by_id,
+            display_names_by_id,
+            new_locations_by_id,
+            offset_ids_by_id,
+            pool_lid_definition,
+            pool_group.lid_labware,
+        )
+    if pool_group.adapter_labware:
+        assert pool_adapter_definition
+        _add_labware_details_to_dicts(
+            definitions_by_id,
+            display_names_by_id,
+            new_locations_by_id,
+            offset_ids_by_id,
+            pool_adapter_definition,
+            pool_group.adapter_labware,
+        )
+
+    _add_labware_details_to_dicts(
+        definitions_by_id,
+        display_names_by_id,
+        new_locations_by_id,
+        offset_ids_by_id,
+        pool_primary_definition,
+        pool_group.primary_labware,
+    )
+
+
+async def build_n_labware_with_ids(
+    pool_primary_definition: LabwareDefinition,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_lid_definition: LabwareDefinition | None,
+    module_id: str,
+    ids: list[StackerStoredLabwareGroup],
+    current_contained_labware: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+) -> tuple[StateUpdate, list[StackerStoredLabwareGroup]]:
+    """Create labware objects to be stored inside the hopper."""
+    pool_groups = [
+        await equipment.load_labware_pool_from_definitions(
+            pool_primary_definition=pool_primary_definition,
+            pool_adapter_definition=pool_adapter_definition,
+            pool_lid_definition=pool_lid_definition,
+            location=InStackerHopperLocation(moduleId=module_id),
+            primary_id=id_group.primaryLabwareId,
+            adapter_id=id_group.adapterLabwareId,
+            lid_id=id_group.lidLabwareId,
+        )
+        for id_group in ids
+    ]
+    definitions_by_id: dict[str, LabwareDefinition] = {}
+    display_names_by_id: dict[str, str | None] = {}
+    new_locations_by_id: dict[str, LabwareLocation] = {}
+    offset_ids_by_id: dict[str, str | None] = {}
+    lid_parent_ids: list[str] = []
+    lid_ids: list[str] = []
+    for pool_group in pool_groups:
+        _add_pool_labware_details_to_dicts(
+            definitions_by_id,
+            display_names_by_id,
+            new_locations_by_id,
+            offset_ids_by_id,
+            lid_parent_ids,
+            lid_ids,
+            pool_primary_definition,
+            pool_lid_definition,
+            pool_adapter_definition,
+            pool_group,
+        )
+    new_contained_labware = current_contained_labware + ids
+    return (
+        StateUpdate()
+        .update_flex_stacker_contained_labware(module_id, new_contained_labware)
+        .set_batch_loaded_labware(
+            definitions_by_id=definitions_by_id,
+            offset_ids_by_id=offset_ids_by_id,
+            display_names_by_id=display_names_by_id,
+            new_locations_by_id=new_locations_by_id,
+        )
+        .set_lids(parent_labware_ids=lid_parent_ids, lid_ids=lid_ids)
+    ), new_contained_labware
+
+
+async def assign_n_labware(
+    pool_primary_definition: LabwareDefinition,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_lid_definition: LabwareDefinition | None,
+    module_id: str,
+    ids: list[StackerStoredLabwareGroup],
+    current_contained_labware: list[StackerStoredLabwareGroup],
+    state_view: StateView,
+) -> tuple[StateUpdate, list[StackerStoredLabwareGroup]]:
+    """Assign a list of labware to be inside the stacker hopper."""
+    check_preloaded_labware(
+        pool_primary_definition,
+        pool_adapter_definition,
+        pool_lid_definition,
+        ids,
+        state_view,
+    )
+
+    def _bottom_labware(group: StackerStoredLabwareGroup) -> str:
+        if group.adapterLabwareId:
+            return group.adapterLabwareId
+        return group.primaryLabwareId
+
+    def _add_ids(
+        group: StackerStoredLabwareGroup, offset_dict: dict[str, str | None]
+    ) -> None:
+        offset_dict[group.primaryLabwareId] = None
+        if group.adapterLabwareId:
+            offset_dict[group.adapterLabwareId] = None
+        if group.lidLabwareId:
+            offset_dict[group.lidLabwareId] = None
+
+    new_locations_by_id = {
+        _bottom_labware(group): InStackerHopperLocation(moduleId=module_id)
+        for group in ids
+    }
+    new_offset_ids_by_id: dict[str, str | None] = {}
+    for group in ids:
+        _add_ids(group, new_offset_ids_by_id)
+
+    new_contained_labware = current_contained_labware + ids
+    return (
+        StateUpdate()
+        .update_flex_stacker_contained_labware(module_id, new_contained_labware)
+        .set_batch_labware_location(
+            new_locations_by_id=new_locations_by_id,
+            new_offset_ids_by_id=new_offset_ids_by_id,
+        )
+    ), new_contained_labware
+
+
+async def build_or_assign_labware_to_hopper(
+    pool_primary_definition: LabwareDefinition,
+    pool_adapter_definition: LabwareDefinition | None,
+    pool_lid_definition: LabwareDefinition | None,
+    module_id: str,
+    ids: list[StackerStoredLabwareGroup],
+    current_contained_labware: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+    state_view: StateView,
+) -> tuple[StateUpdate, list[StackerStoredLabwareGroup]]:
+    """Use the common params to labware-creating stacker commands to load labware appropriately.
+
+    If the specified labware IDs exist already, labware is moved; if they don't, labware is created.
+    """
+    if check_if_labware_preloaded(ids, state_view):
+        return await assign_n_labware(
+            pool_primary_definition,
+            pool_adapter_definition,
+            pool_lid_definition,
+            module_id,
+            ids,
+            current_contained_labware,
+            state_view,
+        )
+    else:
+        return await build_n_labware_with_ids(
+            pool_primary_definition,
+            pool_adapter_definition,
+            pool_lid_definition,
+            module_id,
+            ids,
+            current_contained_labware,
+            equipment,
+        )
+
+
+def _count_from_lw_list_or_initial_count(
+    initial_count: int | None,
+    initial_lw: list[StackerStoredLabwareGroup] | None,
+    max_pool_count: int,
+    current_count: int,
+) -> int:
+    if initial_count is None and initial_lw is None:
+        return max_pool_count - current_count
+    if initial_count is None and initial_lw is not None:
+        if len(initial_lw) > (max_pool_count - current_count):
+            raise CommandPreconditionViolated(
+                f"{len(initial_lw)} labware groups were requested to be stored, but the stacker can store only {max_pool_count}"
+            )
+        return len(initial_lw)
+    if initial_count is not None and initial_lw is None:
+        return min(
+            max(initial_count - current_count, 0), (max_pool_count - current_count)
+        )
+    if initial_count is not None and initial_lw is not None:
+        if len(initial_lw) != initial_count:
+            raise CommandPreconditionViolated(
+                "If initialCount and initialStoredLabware are both specified, the number of labware must equal the count"
+            )
+        if initial_count > (max_pool_count - current_count):
+            raise CommandPreconditionViolated(
+                f"{len(initial_lw)} labware groups were requested to be stored, but the stacker can store only {max_pool_count}"
+            )
+        return initial_count
+    raise CommandPreconditionViolated(
+        f"{initial_lw} and {initial_count} are not valid combinations of initialCount and initialStoredLabware"
+    )
+
+
+def _build_one_labware_group(
+    has_adapter: bool,
+    has_lid: bool,
+    group: StackerStoredLabwareGroup | None,
+    model_utils: ModelUtils,
+) -> StackerStoredLabwareGroup:
+    if group:
+        return group
+    return StackerStoredLabwareGroup(
+        primaryLabwareId=model_utils.generate_id(),
+        adapterLabwareId=(model_utils.generate_id() if has_adapter else None),
+        lidLabwareId=(model_utils.generate_id() if has_lid else None),
+    )
+
+
+def build_ids_to_fill(
+    has_adapter: bool,
+    has_lid: bool,
+    initialLabware: list[StackerStoredLabwareGroup] | None,
+    initialCount: int | None,
+    max_count: int,
+    current_count: int,
+    model_utils: ModelUtils,
+) -> list[StackerStoredLabwareGroup]:
+    """Handle the common params for filling the stacker to make a list of ids.
+
+    Only builds labware to add to the current stored (defined by current count).
+    """
+    count = _count_from_lw_list_or_initial_count(
+        initialCount, initialLabware, max_count, current_count
+    )
+
+    def _pad_labware_to_count(
+        labware_list: list[StackerStoredLabwareGroup], count: int
+    ) -> Sequence[StackerStoredLabwareGroup | None]:
+        if len(labware_list) < count:
+            return labware_list + ([None] * (count - len(labware_list)))
+        else:
+            return labware_list[:count]
+
+    return [
+        _build_one_labware_group(has_adapter, has_lid, group, model_utils)
+        for group in _pad_labware_to_count(initialLabware or [], count)
+    ]
+
+
+def build_retrieve_labware_move_updates(
+    group: StackerStoredLabwareGroup,
+    stacker: FlexStackerSubState,
+    state_view: StateView,
+) -> tuple[dict[str, LabwareLocation], dict[str, str | None]]:
+    """Build the arguments required for batch_labware_location."""
+    locations_for_ids: dict[str, LabwareLocation] = {}
+    offset_ids_by_id: dict[str, str | None] = {}
+    base_offset_location = state_view.geometry.get_projected_offset_location(
+        ModuleLocation(moduleId=stacker.module_id)
+    )
+    assert stacker.pool_primary_definition, "Undefined labware pool"
+    primary_uri = state_view.labware.get_uri_from_definition(
+        stacker.pool_primary_definition
+    )
+
+    def _prepend_loc(
+        new: LabwareOffsetLocationSequence,
+        current: LabwareOffsetLocationSequence | None,
+    ) -> LabwareOffsetLocationSequence | None:
+        if current is None:
+            return None
+        return new + current
+
+    def _find_offset_id(
+        uri: str, offset_location: LabwareOffsetLocationSequence | None
+    ) -> str | None:
+        if offset_location is None:
+            return None
+        offset = state_view.labware.find_applicable_labware_offset(uri, offset_location)
+        if offset is None:
+            return None
+        return offset.id
+
+    if group.adapterLabwareId:
+        locations_for_ids[group.adapterLabwareId] = ModuleLocation(
+            moduleId=stacker.module_id
+        )
+        assert (
+            stacker.pool_adapter_definition
+        ), "Mismatched pool and labware definitions"
+        adapter_uri = state_view.labware.get_uri_from_definition(
+            stacker.pool_adapter_definition
+        )
+        offset_ids_by_id[group.adapterLabwareId] = _find_offset_id(
+            adapter_uri, base_offset_location
+        )
+        primary_offset_location = _prepend_loc(
+            [OnLabwareOffsetLocationSequenceComponent(labwareUri=adapter_uri)],
+            base_offset_location,
+        )
+        offset_ids_by_id[group.primaryLabwareId] = _find_offset_id(
+            primary_uri, primary_offset_location
+        )
+
+    else:
+        locations_for_ids[group.primaryLabwareId] = ModuleLocation(
+            moduleId=stacker.module_id
+        )
+        primary_offset_location = base_offset_location
+        offset_ids_by_id[group.primaryLabwareId] = _find_offset_id(
+            primary_uri, primary_offset_location
+        )
+
+    if group.lidLabwareId:
+        assert (
+            stacker.pool_lid_definition is not None
+        ), "Mismatched pool and stored labware"
+        lid_offset_location = _prepend_loc(
+            [OnLabwareOffsetLocationSequenceComponent(labwareUri=primary_uri)],
+            primary_offset_location,
+        )
+        offset_ids_by_id[group.lidLabwareId] = _find_offset_id(
+            state_view.labware.get_uri_from_definition(stacker.pool_lid_definition),
+            lid_offset_location,
+        )
+    return locations_for_ids, offset_ids_by_id
diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/empty.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/empty.py
index 5f033e6eff6..5a0fb606afe 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/empty.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/empty.py
@@ -2,7 +2,6 @@
 
 from __future__ import annotations
 
-from __future__ import annotations
 from typing import Optional, Literal, TYPE_CHECKING, Annotated
 from typing_extensions import Type
 
@@ -15,8 +14,23 @@
 )
 from ...errors.exceptions import FlexStackerLabwarePoolNotYetDefinedError
 from ...state import update_types
-from ...types import StackerFillEmptyStrategy
-from opentrons.calibration_storage.helpers import uri_from_details
+from ...types import (
+    StackerFillEmptyStrategy,
+    StackerStoredLabwareGroup,
+    NotOnDeckLocationSequenceComponent,
+    OFF_DECK_LOCATION,
+    InStackerHopperLocation,
+    LabwareLocationSequence,
+    OnLabwareLocation,
+    LabwareLocation,
+)
+from .common import (
+    labware_locations_for_group,
+    primary_location_sequences,
+    adapter_location_sequences_with_default,
+    lid_location_sequences_with_default,
+)
+
 
 if TYPE_CHECKING:
     from ...state.state import StateView
@@ -74,6 +88,49 @@ class EmptyResult(BaseModel):
         None,
         description="The labware definition URI of the lid labware.",
     )
+    storedLabware: list[StackerStoredLabwareGroup] | SkipJsonSchema[None] = Field(
+        ..., description="The primary labware loaded into the stacker labware pool."
+    )
+    removedLabware: list[StackerStoredLabwareGroup] | SkipJsonSchema[None] = Field(
+        ...,
+        description="The labware objects that have just been removed from the stacker labware pool.",
+    )
+    originalPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each newly-removed primary labware, in the same order as removedLabware.",
+    )
+    originalAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each newly-removed adapter labware, in the same order as removedLabware. None if the pool does not specify an adapter.",
+    )
+    originalLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each newly-removed lid labware, in the same order as removedLabware. None if the  pool does not specify a lid.",
+    )
+    newPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each newly-removed primary labware, in the same order as removedLabware.",
+    )
+    newAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each newly-removed adapter labware, in the same order as removedLabware. None if the pool does not specify an adapter.",
+    )
+    newLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each newly-removed lid labware, in the same order as removedLabware. None if the pool does not specify a lid labware.",
+    )
 
 
 class EmptyImpl(AbstractCommandImpl[EmptyParams, SuccessData[EmptyResult]]):
@@ -99,11 +156,42 @@ async def execute(self, params: EmptyParams) -> SuccessData[EmptyResult]:
 
         count = params.count if params.count is not None else 0
 
-        new_count = min(stacker_state.pool_count, count)
+        new_count = min(len(stacker_state.contained_labware_bottom_first), count)
+
+        new_stored_labware = stacker_state.contained_labware_bottom_first[:new_count]
+        removed_labware = stacker_state.contained_labware_bottom_first[new_count:]
+        new_locations_by_id: dict[str, LabwareLocation] = {}
+        new_offset_ids_by_id: dict[str, str | None] = {}
+
+        def _add_to_dicts(labware_group: StackerStoredLabwareGroup) -> None:
+            if labware_group.adapterLabwareId:
+                new_locations_by_id[labware_group.primaryLabwareId] = OnLabwareLocation(
+                    labwareId=labware_group.adapterLabwareId
+                )
+                new_locations_by_id[labware_group.adapterLabwareId] = OFF_DECK_LOCATION
+                new_offset_ids_by_id[labware_group.primaryLabwareId] = None
+                new_offset_ids_by_id[labware_group.adapterLabwareId] = None
+            else:
+                new_locations_by_id[labware_group.primaryLabwareId] = OFF_DECK_LOCATION
+                new_offset_ids_by_id[labware_group.primaryLabwareId] = None
+            if labware_group.lidLabwareId:
+                new_locations_by_id[labware_group.lidLabwareId] = OnLabwareLocation(
+                    labwareId=labware_group.primaryLabwareId
+                )
+                new_offset_ids_by_id[labware_group.lidLabwareId] = None
+
+        for group in removed_labware:
+            _add_to_dicts(group)
 
         state_update = (
-            update_types.StateUpdate().update_flex_stacker_labware_pool_count(
-                params.moduleId, new_count
+            update_types.StateUpdate()
+            .update_flex_stacker_contained_labware(
+                module_id=params.moduleId,
+                contained_labware_bottom_first=new_stored_labware,
+            )
+            .set_batch_labware_location(
+                new_locations_by_id=new_locations_by_id,
+                new_offset_ids_by_id=new_offset_ids_by_id,
             )
         )
 
@@ -115,28 +203,56 @@ async def execute(self, params: EmptyParams) -> SuccessData[EmptyResult]:
                 "The Primary Labware must be defined in the stacker pool."
             )
 
+        original_locations = [
+            labware_locations_for_group(
+                group, [InStackerHopperLocation(moduleId=params.moduleId)]
+            )
+            for group in removed_labware
+        ]
+        new_locations = [
+            labware_locations_for_group(
+                group,
+                [
+                    NotOnDeckLocationSequenceComponent(
+                        logicalLocationName=OFF_DECK_LOCATION
+                    )
+                ],
+            )
+            for group in removed_labware
+        ]
+
         return SuccessData(
-            public=EmptyResult(
+            public=EmptyResult.model_construct(
                 count=new_count,
-                primaryLabwareURI=uri_from_details(
-                    stacker_state.pool_primary_definition.namespace,
-                    stacker_state.pool_primary_definition.parameters.loadName,
-                    stacker_state.pool_primary_definition.version,
+                primaryLabwareURI=self._state_view.labware.get_uri_from_definition(
+                    stacker_state.pool_primary_definition
+                ),
+                adapterLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_adapter_definition
+                ),
+                lidLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_lid_definition
+                ),
+                storedLabware=new_stored_labware,
+                removedLabware=removed_labware,
+                originalPrimaryLabwareLocationSequences=primary_location_sequences(
+                    original_locations
+                ),
+                originalAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    original_locations, stacker_state.pool_adapter_definition
+                ),
+                originalLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    original_locations, stacker_state.pool_lid_definition
+                ),
+                newPrimaryLabwareLocationSequences=primary_location_sequences(
+                    new_locations
+                ),
+                newAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    new_locations, stacker_state.pool_adapter_definition
+                ),
+                newLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    new_locations, stacker_state.pool_lid_definition
                 ),
-                adapterLabwareURI=uri_from_details(
-                    stacker_state.pool_adapter_definition.namespace,
-                    stacker_state.pool_adapter_definition.parameters.loadName,
-                    stacker_state.pool_adapter_definition.version,
-                )
-                if stacker_state.pool_adapter_definition is not None
-                else None,
-                lidLabwareURI=uri_from_details(
-                    stacker_state.pool_lid_definition.namespace,
-                    stacker_state.pool_lid_definition.parameters.loadName,
-                    stacker_state.pool_lid_definition.version,
-                )
-                if stacker_state.pool_lid_definition is not None
-                else None,
             ),
             state_update=state_update,
         )
diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/fill.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/fill.py
index 921f242230b..99d2a571082 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/fill.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/fill.py
@@ -12,13 +12,31 @@
     ErrorOccurrence,
 )
 from ...errors.exceptions import FlexStackerLabwarePoolNotYetDefinedError
-from ...state import update_types
-from ...types import StackerFillEmptyStrategy
-from opentrons.calibration_storage.helpers import uri_from_details
+from ...types import (
+    StackerFillEmptyStrategy,
+    StackerStoredLabwareGroup,
+    LabwareLocationSequence,
+    NotOnDeckLocationSequenceComponent,
+    SYSTEM_LOCATION,
+    InStackerHopperLocation,
+)
+from .common import (
+    INITIAL_COUNT_DESCRIPTION,
+    INITIAL_STORED_LABWARE_DESCRIPTION,
+    build_ids_to_fill,
+    build_or_assign_labware_to_hopper,
+    labware_locations_for_group,
+    labware_location_base_sequence,
+    primary_location_sequences,
+    adapter_location_sequences_with_default,
+    lid_location_sequences_with_default,
+)
 
 if TYPE_CHECKING:
-    from ...state.state import StateView
-    from ...execution import RunControlHandler
+    from opentrons.protocol_engine.state.state import StateView
+    from opentrons.protocol_engine.execution import EquipmentHandler, RunControlHandler
+    from opentrons.protocol_engine.resources import ModelUtils
+
 
 FillCommandType = Literal["flexStacker/fill"]
 
@@ -44,15 +62,10 @@ class FillParams(BaseModel):
     )
 
     count: Optional[Annotated[int, Field(ge=1)]] = Field(
-        None,
-        description=(
-            "How full the labware pool should now be. If None, default to the maximum amount "
-            "of the currently-configured labware the pool can hold. "
-            "If this number is larger than the maximum the pool can hold, it will be clamped to "
-            "the maximum. If this number is smaller than the current amount of labware the pool "
-            "holds, it will be clamped to that minimum. Do not use the value in the parameters as "
-            "an outside observer; instead, use the count value from the results."
-        ),
+        None, description=INITIAL_COUNT_DESCRIPTION
+    )
+    labwareToStore: list[StackerStoredLabwareGroup] | None = Field(
+        None, description=INITIAL_STORED_LABWARE_DESCRIPTION
     )
 
 
@@ -60,11 +73,10 @@ class FillResult(BaseModel):
     """Result data from a stacker fill command."""
 
     count: int = Field(
-        ..., description="The new amount of labware stored in the stacker labware pool."
+        ..., description="The new amount of labware stored in the stacker."
     )
     primaryLabwareURI: str = Field(
-        ...,
-        description="The labware definition URI of the primary labware.",
+        ..., description="The labware definition URI of the primary labware."
     )
     adapterLabwareURI: str | SkipJsonSchema[None] = Field(
         None,
@@ -74,16 +86,65 @@ class FillResult(BaseModel):
         None,
         description="The labware definition URI of the lid labware.",
     )
+    storedLabware: list[StackerStoredLabwareGroup] | SkipJsonSchema[None] = Field(
+        None, description="The labware now stored in the stacker."
+    )
+    addedLabware: list[StackerStoredLabwareGroup] | SkipJsonSchema[None] = Field(
+        None, description="The labware just added to the stacker."
+    )
+    originalPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each primary labware, in the same order as storedLabware.",
+    )
+    originalAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each adapter labware, in the same order as storedLabware. None if the pool does not specify an adapter.",
+    )
+    originalLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each lid labware, in the same order as storedLabware. None if the pool does not specify a lid.",
+    )
+    newPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each primary labware, in the same order as storedLabware.",
+    )
+    newAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each adapter labware, in the same order as storedLabware. None if the pool does not specify an adapter.",
+    )
+    newLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each lid labware, in the same order as storedLabware. None if the pool does not specify a lid labware.",
+    )
 
 
 class FillImpl(AbstractCommandImpl[FillParams, SuccessData[FillResult]]):
     """Implementation of a stacker fill command."""
 
     def __init__(
-        self, state_view: StateView, run_control: RunControlHandler, **kwargs: object
+        self,
+        state_view: StateView,
+        run_control: RunControlHandler,
+        model_utils: ModelUtils,
+        equipment: EquipmentHandler,
+        **kwargs: object,
     ) -> None:
         self._state_view = state_view
         self._run_control = run_control
+        self._model_utils = model_utils
+        self._equipment = equipment
 
     async def execute(self, params: FillParams) -> SuccessData[FillResult]:
         """Execute the stacker fill command."""
@@ -97,49 +158,89 @@ async def execute(self, params: FillParams) -> SuccessData[FillResult]:
                 message=f"The Flex Stacker in {location} has not been configured yet and cannot be filled."
             )
 
-        count = (
-            params.count if params.count is not None else stacker_state.max_pool_count
-        )
-        new_count = min(
-            stacker_state.max_pool_count, max(stacker_state.pool_count, count)
+        groups_to_load = build_ids_to_fill(
+            stacker_state.pool_adapter_definition is not None,
+            stacker_state.pool_lid_definition is not None,
+            params.labwareToStore,
+            params.count,
+            stacker_state.max_pool_count,
+            len(stacker_state.contained_labware_bottom_first),
+            self._model_utils,
         )
 
-        state_update = (
-            update_types.StateUpdate().update_flex_stacker_labware_pool_count(
-                params.moduleId, new_count
-            )
+        state_update, new_contained_labware = await build_or_assign_labware_to_hopper(
+            stacker_state.pool_primary_definition,
+            stacker_state.pool_adapter_definition,
+            stacker_state.pool_lid_definition,
+            params.moduleId,
+            groups_to_load,
+            stacker_state.get_contained_labware(),
+            self._equipment,
+            self._state_view,
         )
+        added_labware = [
+            labware
+            for labware in new_contained_labware
+            if labware not in stacker_state.contained_labware_bottom_first
+        ]
+
+        original_location_sequences = [
+            labware_locations_for_group(
+                group,
+                labware_location_base_sequence(
+                    group,
+                    self._state_view,
+                    [
+                        NotOnDeckLocationSequenceComponent(
+                            logicalLocationName=SYSTEM_LOCATION
+                        )
+                    ],
+                ),
+            )
+            for group in added_labware
+        ]
+        new_location_sequences = [
+            labware_locations_for_group(
+                group, [InStackerHopperLocation(moduleId=params.moduleId)]
+            )
+            for group in added_labware
+        ]
 
         if params.strategy == StackerFillEmptyStrategy.MANUAL_WITH_PAUSE:
             await self._run_control.wait_for_resume()
 
-        if stacker_state.pool_primary_definition is None:
-            raise FlexStackerLabwarePoolNotYetDefinedError(
-                "The Primary Labware must be defined in the stacker pool."
-            )
-
         return SuccessData(
-            public=FillResult(
-                count=new_count,
-                primaryLabwareURI=uri_from_details(
-                    stacker_state.pool_primary_definition.namespace,
-                    stacker_state.pool_primary_definition.parameters.loadName,
-                    stacker_state.pool_primary_definition.version,
+            public=FillResult.model_construct(
+                count=len(new_contained_labware),
+                storedLabware=new_contained_labware,
+                addedLabware=added_labware,
+                primaryLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_primary_definition
+                ),
+                adapterLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_adapter_definition
+                ),
+                lidLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_lid_definition
+                ),
+                originalPrimaryLabwareLocationSequences=primary_location_sequences(
+                    original_location_sequences
+                ),
+                originalAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    original_location_sequences, stacker_state.pool_adapter_definition
+                ),
+                originalLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    original_location_sequences, stacker_state.pool_lid_definition
+                ),
+                newPrimaryLabwareLocationSequences=primary_location_sequences(
+                    new_location_sequences
+                ),
+                newAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    new_location_sequences, stacker_state.pool_adapter_definition
+                ),
+                newLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    new_location_sequences, stacker_state.pool_lid_definition
                 ),
-                adapterLabwareURI=uri_from_details(
-                    stacker_state.pool_adapter_definition.namespace,
-                    stacker_state.pool_adapter_definition.parameters.loadName,
-                    stacker_state.pool_adapter_definition.version,
-                )
-                if stacker_state.pool_adapter_definition is not None
-                else None,
-                lidLabwareURI=uri_from_details(
-                    stacker_state.pool_lid_definition.namespace,
-                    stacker_state.pool_lid_definition.parameters.loadName,
-                    stacker_state.pool_lid_definition.version,
-                )
-                if stacker_state.pool_lid_definition is not None
-                else None,
             ),
             state_update=state_update,
         )
diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/retrieve.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/retrieve.py
index 82ec6936222..15a8305a7f5 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/retrieve.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/retrieve.py
@@ -1,12 +1,18 @@
 """Command models to retrieve a labware from a Flex Stacker."""
 
 from __future__ import annotations
-from typing import Literal, TYPE_CHECKING, Any, Union, Optional
+from typing import Literal, TYPE_CHECKING, Any, Union
 from typing_extensions import Type
 
 from pydantic import BaseModel, Field
 from pydantic.json_schema import SkipJsonSchema
 
+from opentrons_shared_data.errors.exceptions import (
+    FlexStackerStallError,
+    FlexStackerShuttleMissingError,
+    FlexStackerHopperLabwareError,
+)
+
 from ..command import (
     AbstractCommandImpl,
     BaseCommand,
@@ -14,11 +20,6 @@
     SuccessData,
     DefinedErrorData,
 )
-from ..flex_stacker.common import (
-    FlexStackerStallOrCollisionError,
-    FlexStackerShuttleError,
-    FlexStackerHopperError,
-)
 from ...errors import (
     ErrorOccurrence,
     CannotPerformModuleAction,
@@ -30,19 +31,21 @@
 from ...types import (
     ModuleLocation,
     LabwareLocationSequence,
-    LabwareLocation,
-    LoadedLabware,
+    InStackerHopperLocation,
 )
-from opentrons_shared_data.labware.labware_definition import LabwareDefinition
-from opentrons_shared_data.errors.exceptions import (
-    FlexStackerStallError,
-    FlexStackerShuttleMissingError,
-    FlexStackerHopperLabwareError,
+from .common import (
+    labware_locations_for_group,
+    build_retrieve_labware_move_updates,
+    FlexStackerStallOrCollisionError,
+    FlexStackerShuttleError,
+    FlexStackerHopperError,
+    primary_location_sequence,
+    adapter_location_sequence,
+    lid_location_sequence,
 )
 
 if TYPE_CHECKING:
     from opentrons.protocol_engine.state.state import StateView
-    from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
     from opentrons.protocol_engine.execution import EquipmentHandler
 
 RetrieveCommandType = Literal["flexStacker/retrieve"]
@@ -67,26 +70,22 @@ class RetrieveParams(BaseModel):
     )
     labwareId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to this labware. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     displayName: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional user-specified display name "
-        "or label for this labware.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     adapterId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to an adapter. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     lidId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to a lid. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
 
@@ -107,14 +106,24 @@ class RetrieveResult(BaseModel):
         description="The optional Lid Labware ID of the lid on a primary labware.",
     )
     primaryLocationSequence: LabwareLocationSequence = Field(
-        ..., description="The origin location of the primary labware."
+        ..., description="The new location of the just-retrieved."
     )
     lidLocationSequence: LabwareLocationSequence | SkipJsonSchema[None] = Field(
         None,
-        description="The origin location of the adapter labware under a primary labware.",
+        description="The new location of the just-retrieved adapter labware under a primary labware.",
     )
     adapterLocationSequence: LabwareLocationSequence | SkipJsonSchema[None] = Field(
-        None, description="The origin location of the lid labware on a primary labware."
+        None,
+        description="The new location of the just-retrieved lid labware on a primary labware.",
+    )
+    originalPrimaryLocationSequence: LabwareLocationSequence = Field(
+        ..., description="The original location of the just-retrieved primary labware"
+    )
+    originalAdapterLocationSequence: LabwareLocationSequence | SkipJsonSchema[
+        None
+    ] = Field(None, description="The original location of an adapter labware if any")
+    originalLidLocationSequence: LabwareLocationSequence | SkipJsonSchema[None] = Field(
+        None, description="The original location of a lid labware if any"
     )
     primaryLabwareURI: str = Field(
         ...,
@@ -152,132 +161,13 @@ def __init__(
         self._equipment = equipment
         self._model_utils = model_utils
 
-    async def _load_labware_from_pool(
-        self, params: RetrieveParams, stacker_state: FlexStackerSubState
-    ) -> tuple[RetrieveResult, update_types.StateUpdate]:
-        state_update = update_types.StateUpdate()
-
-        # Always load the primary labware
-        if stacker_state.pool_primary_definition is None:
-            raise CannotPerformModuleAction(
-                f"Flex Stacker {params.moduleId} has no labware to retrieve"
-            )
-
-        # Load the Stacker Pool Labwares
-        loaded_labware_pool = await self._equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=stacker_state.pool_primary_definition,
-            pool_adapter_definition=stacker_state.pool_adapter_definition,
-            pool_lid_definition=stacker_state.pool_lid_definition,
-            location=ModuleLocation(moduleId=params.moduleId),
-            primary_id=params.labwareId,
-            adapter_id=params.labwareId,
-            lid_id=params.lidId,
-        )
-
-        # Dictionaries for state updates and location sequencing
-        definitions_by_id: dict[str, LabwareDefinition] = {}
-        definitions_by_id[
-            loaded_labware_pool.primary_labware.id
-        ] = stacker_state.pool_primary_definition
-        offset_ids_by_id: dict[str, Optional[str]] = {}
-        display_names_by_id: dict[str, str | None] = {}
-        new_locations_by_id: dict[str, LabwareLocation] = {}
-        labware_by_id: dict[str, LoadedLabware] = {}
-
-        for loaded_labware in (
-            loaded_labware_pool.primary_labware,
-            loaded_labware_pool.adapter_labware,
-            loaded_labware_pool.lid_labware,
-        ):
-            if loaded_labware:
-                offset_ids_by_id[loaded_labware.id] = loaded_labware.offsetId
-                display_names_by_id[loaded_labware.id] = loaded_labware.displayName
-                new_locations_by_id[loaded_labware.id] = loaded_labware.location
-                labware_by_id[loaded_labware.id] = loaded_labware
-        if (
-            loaded_labware_pool.adapter_labware
-            and stacker_state.pool_adapter_definition is not None
-        ):
-            definitions_by_id[
-                loaded_labware_pool.adapter_labware.id
-            ] = stacker_state.pool_adapter_definition
-        if (
-            loaded_labware_pool.lid_labware
-            and stacker_state.pool_lid_definition is not None
-        ):
-            definitions_by_id[
-                loaded_labware_pool.lid_labware.id
-            ] = stacker_state.pool_lid_definition
-
-        # Get the labware dimensions for the labware being retrieved,
-        # which is the first one in the hopper labware id list
-        primary_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.primary_labware.id],
-                labware_by_id,
-            )
-        )
-        adapter_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.adapter_labware.id],
-                labware_by_id,
-            )
-            if loaded_labware_pool.adapter_labware is not None
-            else None
-        )
-        lid_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.lid_labware.id],
-                labware_by_id,
-            )
-            if loaded_labware_pool.lid_labware is not None
-            else None
-        )
-
-        state_update.set_batch_loaded_labware(
-            definitions_by_id=definitions_by_id,
-            display_names_by_id=display_names_by_id,
-            offset_ids_by_id=offset_ids_by_id,
-            new_locations_by_id=new_locations_by_id,
-        )
-        state_update.update_flex_stacker_labware_pool_count(
-            module_id=params.moduleId, count=stacker_state.pool_count - 1
-        )
-
-        if loaded_labware_pool.lid_labware is not None:
-            state_update.set_lids(
-                parent_labware_ids=[loaded_labware_pool.primary_labware.id],
-                lid_ids=[loaded_labware_pool.lid_labware.id],
-            )
-
-        return (
-            RetrieveResult(
-                labwareId=loaded_labware_pool.primary_labware.id,
-                adapterId=loaded_labware_pool.adapter_labware.id
-                if loaded_labware_pool.adapter_labware is not None
-                else None,
-                lidId=loaded_labware_pool.lid_labware.id
-                if loaded_labware_pool.lid_labware is not None
-                else None,
-                primaryLocationSequence=primary_location_sequence,
-                adapterLocationSequence=adapter_location_sequence,
-                lidLocationSequence=lid_location_sequence,
-                primaryLabwareURI=loaded_labware_pool.primary_labware.definitionUri,
-                adapterLabwareURI=loaded_labware_pool.adapter_labware.definitionUri
-                if loaded_labware_pool.adapter_labware is not None
-                else None,
-                lidLabwareURI=loaded_labware_pool.lid_labware.definitionUri
-                if loaded_labware_pool.lid_labware is not None
-                else None,
-            ),
-            state_update,
-        )
-
     def handle_recoverable_error(
         self, error: RecoverableExceptions
-    ) -> DefinedErrorData[FlexStackerStallOrCollisionError] | DefinedErrorData[
-        FlexStackerShuttleError
-    ] | DefinedErrorData[FlexStackerHopperError]:
+    ) -> (
+        DefinedErrorData[FlexStackerStallOrCollisionError]
+        | DefinedErrorData[FlexStackerShuttleError]
+        | DefinedErrorData[FlexStackerHopperError]
+    ):
         """Handle a recoverable error raised during command execution."""
         error_map = {
             FlexStackerStallError: FlexStackerStallOrCollisionError,
@@ -303,17 +193,16 @@ async def execute(self, params: RetrieveParams) -> _ExecuteReturn:
         stacker_state = self._state_view.modules.get_flex_stacker_substate(
             params.moduleId
         )
+        location = self._state_view.modules.get_location(params.moduleId)
 
-        pool_definitions = stacker_state.get_pool_definition_ordered_list()
-        if pool_definitions is None:
-            location = self._state_view.modules.get_location(params.moduleId)
+        if stacker_state.pool_primary_definition is None:
             raise FlexStackerLabwarePoolNotYetDefinedError(
-                message=f"The Flex Stacker in {location} has not been configured yet and cannot be filled."
+                f"The Flex Stacker in {location} must be configured before labware can be retrieved."
             )
 
-        if stacker_state.pool_count == 0:
+        if len(stacker_state.contained_labware_bottom_first) == 0:
             raise CannotPerformModuleAction(
-                message="Cannot retrieve labware from Flex Stacker because it contains no labware"
+                message=f"Cannot retrieve labware from Flex Stacker in {location} because it contains no labware"
             )
 
         stacker_loc = ModuleLocation(moduleId=params.moduleId)
@@ -322,11 +211,11 @@ async def execute(self, params: RetrieveParams) -> _ExecuteReturn:
             self._state_view.labware.raise_if_labware_in_location(stacker_loc)
         except LocationIsOccupiedError:
             raise CannotPerformModuleAction(
-                "Cannot retrieve a labware from Flex Stacker if the carriage is occupied"
+                f"Cannot retrieve a labware from Flex Stacker in {location} if the carriage is occupied"
             )
 
-        labware_height = self._state_view.geometry.get_height_of_labware_stack(
-            definitions=pool_definitions
+        labware_height = self._state_view.geometry.get_height_of_stacker_labware_pool(
+            params.moduleId
         )
 
         # Allow propagation of ModuleNotAttachedError.
@@ -341,8 +230,11 @@ async def execute(self, params: RetrieveParams) -> _ExecuteReturn:
             ) as e:
                 return self.handle_recoverable_error(e)
 
-        retrieve_result, state_update = await self._load_labware_from_pool(
-            params, stacker_state
+        to_retrieve = stacker_state.contained_labware_bottom_first[0]
+        remaining = stacker_state.contained_labware_bottom_first[1:]
+
+        locations, offsets = build_retrieve_labware_move_updates(
+            to_retrieve, stacker_state, self._state_view
         )
 
         # Update the state to reflect the labware is now in the Flex Stacker slot
@@ -357,11 +249,49 @@ async def execute(self, params: RetrieveParams) -> _ExecuteReturn:
                 model=self._state_view.modules.get(params.moduleId).model,
             )
         )
-        state_update.set_addressable_area_used(stacker_area)
+        original_locations = labware_locations_for_group(
+            to_retrieve, [InStackerHopperLocation(moduleId=params.moduleId)]
+        )
+        new_locations = labware_locations_for_group(
+            to_retrieve,
+            self._state_view.geometry.get_predicted_location_sequence(
+                ModuleLocation(moduleId=params.moduleId)
+            ),
+        )
 
         return SuccessData(
-            public=retrieve_result,
-            state_update=state_update,
+            public=RetrieveResult.model_construct(
+                labwareId=to_retrieve.primaryLabwareId,
+                adapterId=to_retrieve.adapterLabwareId,
+                lidId=to_retrieve.lidLabwareId,
+                primaryLocationSequence=primary_location_sequence(new_locations),
+                adapterLocationSequence=adapter_location_sequence(new_locations),
+                lidLocationSequence=lid_location_sequence(new_locations),
+                originalPrimaryLocationSequence=primary_location_sequence(
+                    original_locations
+                ),
+                originalAdapterLocationSequence=adapter_location_sequence(
+                    original_locations
+                ),
+                originalLidLocationSequence=lid_location_sequence(original_locations),
+                primaryLabwareURI=self._state_view.labware.get_uri_from_definition(
+                    stacker_state.pool_primary_definition
+                ),
+                adapterLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_adapter_definition
+                ),
+                lidLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_lid_definition
+                ),
+            ),
+            state_update=(
+                update_types.StateUpdate()
+                .set_batch_labware_location(
+                    new_locations_by_id=locations, new_offset_ids_by_id=offsets
+                )
+                .update_flex_stacker_contained_labware(params.moduleId, remaining)
+                .set_addressable_area_used(stacker_area)
+            ),
         )
 
 
diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py
index 7fa71b70781..c9afd4a0c3b 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py
@@ -14,11 +14,29 @@
     ErrorOccurrence,
 )
 from ...errors.exceptions import FlexStackerNotLogicallyEmptyError
-from ...state import update_types
+from ...types import (
+    StackerStoredLabwareGroup,
+    LabwareLocationSequence,
+    NotOnDeckLocationSequenceComponent,
+    InStackerHopperLocation,
+    SYSTEM_LOCATION,
+)
+from .common import (
+    INITIAL_COUNT_DESCRIPTION,
+    INITIAL_STORED_LABWARE_DESCRIPTION,
+    build_ids_to_fill,
+    build_or_assign_labware_to_hopper,
+    labware_locations_for_group,
+    labware_location_base_sequence,
+    primary_location_sequences,
+    adapter_location_sequences_with_default,
+    lid_location_sequences_with_default,
+)
 
 if TYPE_CHECKING:
     from opentrons.protocol_engine.state.state import StateView
     from opentrons.protocol_engine.execution import EquipmentHandler
+    from opentrons.protocol_engine.resources import ModelUtils
 
 SetStoredLabwareCommandType = Literal["flexStacker/setStoredLabware"]
 
@@ -56,10 +74,11 @@ class SetStoredLabwareParams(BaseModel):
     )
     initialCount: Optional[Annotated[int, Field(ge=0)]] = Field(
         None,
-        description=(
-            "The number of labware that should be initially stored in the stacker. This number will be silently clamped to "
-            "the maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker."
-        ),
+        description=INITIAL_COUNT_DESCRIPTION,
+    )
+    initialStoredLabware: list[StackerStoredLabwareGroup] | None = Field(
+        None,
+        description=INITIAL_STORED_LABWARE_DESCRIPTION,
     )
 
 
@@ -76,8 +95,47 @@ class SetStoredLabwareResult(BaseModel):
         None,
         description="The definition of the adapter under the primary labware, if any.",
     )
+    storedLabware: list[StackerStoredLabwareGroup] = Field(
+        ..., description="The primary labware loaded into the stacker labware pool."
+    )
     count: int = Field(
-        ..., description="The number of labware loaded into the stacker labware pool."
+        ..., description="The number of labware now stored in the hopper."
+    )
+    originalPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each primary labware, in the same order as storedLabware.",
+    )
+    originalAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each adapter labware, in the same order as storedLabware. None if the pool does not specify an adapter.",
+    )
+    originalLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The previous position of each lid labware, in the same order as storedLabware. None if the pool does not specify a lid.",
+    )
+    newPrimaryLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each primary labware, in the same order as storedLabware.",
+    )
+    newAdapterLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each adapter labware, in the same order as storedLabware. None if the pool does not specify an adapter.",
+    )
+    newLidLabwareLocationSequences: (
+        list[LabwareLocationSequence] | SkipJsonSchema[None]
+    ) = Field(
+        None,
+        description="The new position of each lid labware, in the same order as storedLabware. None if the pool does not specify a lid labware.",
     )
 
 
@@ -90,10 +148,12 @@ def __init__(
         self,
         state_view: StateView,
         equipment: EquipmentHandler,
+        model_utils: ModelUtils,
         **kwargs: object,
     ) -> None:
         self._state_view = state_view
         self._equipment = equipment
+        self._model_utils = model_utils
 
     async def execute(
         self, params: SetStoredLabwareParams
@@ -102,16 +162,17 @@ async def execute(
         stacker_state = self._state_view.modules.get_flex_stacker_substate(
             params.moduleId
         )
+        location = self._state_view.modules.get_location(params.moduleId)
 
-        if stacker_state.pool_count != 0:
+        if len(stacker_state.contained_labware_bottom_first) != 0:
             # Note: this error catches if the protocol tells us the stacker is not empty, making this command
             # invalid at this point in the protocol. This error is not recoverable and should occur during
             # analysis; the protocol must be changed.
 
             raise FlexStackerNotLogicallyEmptyError(
                 message=(
-                    "The Flex Stacker must be known to be empty before reconfiguring its labware pool, but it has "
-                    f"a pool of {stacker_state.pool_count} labware"
+                    f"The Flex Stacker in {location} must be known to be empty before reconfiguring its labware pool, but it has "
+                    f"{len(stacker_state.contained_labware_bottom_first)} labware"
                 )
             )
 
@@ -155,29 +216,80 @@ async def execute(
             pool_height,
             overlap.z,
         )
-        initial_count = (
-            params.initialCount if params.initialCount is not None else max_pool_count
+        groups_to_load = build_ids_to_fill(
+            params.adapterLabware is not None,
+            params.lidLabware is not None,
+            params.initialStoredLabware,
+            params.initialCount,
+            max_pool_count,
+            0,
+            self._model_utils,
         )
-        count = min(initial_count, max_pool_count)
-
-        state_update = (
-            update_types.StateUpdate()
-            .update_flex_stacker_labware_pool_definition(
-                params.moduleId,
-                max_pool_count,
-                overlap.z,
-                labware_def,
-                adapter_def,
-                lid_def,
-            )
-            .update_flex_stacker_labware_pool_count(params.moduleId, count)
+        state_update, new_contained_labware = await build_or_assign_labware_to_hopper(
+            labware_def,
+            adapter_def,
+            lid_def,
+            params.moduleId,
+            groups_to_load,
+            [],
+            self._equipment,
+            self._state_view,
         )
+
+        state_update = state_update.update_flex_stacker_labware_pool_definition(
+            params.moduleId,
+            max_pool_count,
+            overlap.z,
+            labware_def,
+            adapter_def,
+            lid_def,
+        )
+        original_location_sequences = [
+            labware_locations_for_group(
+                group,
+                labware_location_base_sequence(
+                    group,
+                    self._state_view,
+                    [
+                        NotOnDeckLocationSequenceComponent(
+                            logicalLocationName=SYSTEM_LOCATION
+                        )
+                    ],
+                ),
+            )
+            for group in new_contained_labware
+        ]
+        new_location_sequences = [
+            labware_locations_for_group(
+                group, [InStackerHopperLocation(moduleId=params.moduleId)]
+            )
+            for group in new_contained_labware
+        ]
         return SuccessData(
             public=SetStoredLabwareResult.model_construct(
                 primaryLabwareDefinition=labware_def,
                 lidLabwareDefinition=lid_def,
                 adapterLabwareDefinition=adapter_def,
-                count=count,
+                count=len(new_contained_labware),
+                storedLabware=new_contained_labware,
+                originalPrimaryLabwareLocationSequences=primary_location_sequences(
+                    original_location_sequences
+                ),
+                originalAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    original_location_sequences, adapter_def
+                ),
+                originalLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    original_location_sequences, lid_def
+                ),
+                newPrimaryLabwareLocationSequences=primary_location_sequences(
+                    new_location_sequences
+                ),
+                newAdapterLabwareLocationSequences=adapter_location_sequences_with_default(
+                    new_location_sequences, adapter_def
+                ),
+                newLidLabwareLocationSequences=lid_location_sequences_with_default(
+                    new_location_sequences, lid_def
+                ),
             ),
             state_update=state_update,
         )
diff --git a/api/src/opentrons/protocol_engine/commands/flex_stacker/store.py b/api/src/opentrons/protocol_engine/commands/flex_stacker/store.py
index 806f63dbfba..2613dd81026 100644
--- a/api/src/opentrons/protocol_engine/commands/flex_stacker/store.py
+++ b/api/src/opentrons/protocol_engine/commands/flex_stacker/store.py
@@ -1,11 +1,17 @@
 """Command models to retrieve a labware from a Flex Stacker."""
 
 from __future__ import annotations
-from typing import Optional, Literal, TYPE_CHECKING, Type, Union
+from typing import Optional, Literal, TYPE_CHECKING, Type, Union, cast
 
 from pydantic import BaseModel, Field
 from pydantic.json_schema import SkipJsonSchema
 
+from opentrons_shared_data.labware.labware_definition import LabwareDefinition
+from opentrons_shared_data.errors.exceptions import (
+    FlexStackerStallError,
+    FlexStackerShuttleMissingError,
+)
+
 from ..command import (
     AbstractCommandImpl,
     BaseCommand,
@@ -16,6 +22,11 @@
 from ..flex_stacker.common import (
     FlexStackerStallOrCollisionError,
     FlexStackerShuttleError,
+    labware_locations_for_group,
+    labware_location_base_sequence,
+    primary_location_sequence,
+    adapter_location_sequence,
+    lid_location_sequence,
 )
 from ...errors import (
     ErrorOccurrence,
@@ -28,15 +39,10 @@
 from ...types import (
     LabwareLocationSequence,
     InStackerHopperLocation,
+    StackerStoredLabwareGroup,
+    ModuleLocation,
 )
 
-from opentrons_shared_data.errors.exceptions import (
-    FlexStackerStallError,
-    FlexStackerShuttleMissingError,
-)
-from opentrons.calibration_storage.helpers import uri_from_details
-
-
 if TYPE_CHECKING:
     from opentrons.protocol_engine.state.state import StateView
     from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
@@ -122,11 +128,12 @@ def __init__(
     def _verify_labware_to_store(
         self, params: StoreParams, stacker_state: FlexStackerSubState
     ) -> tuple[str, str | None, str | None]:
+        location = self._state_view.modules.get_location(params.moduleId)
         try:
             bottom_id = self._state_view.labware.get_id_by_module(params.moduleId)
         except LabwareNotLoadedOnModuleError:
             raise CannotPerformModuleAction(
-                "Cannot store labware if Flex Stacker carriage is empty"
+                f"Flex Stacker in {location} cannot store labware because its carriage is empty"
             )
         labware_ids = self._state_view.labware.get_labware_stack_from_parent(bottom_id)
         labware_defs = [
@@ -139,12 +146,12 @@ def _verify_labware_to_store(
         assert pool_list is not None
         if len(labware_ids) != len(pool_list):
             raise CannotPerformModuleAction(
-                "Cannot store labware stack that does not correspond with Flex Stacker configuration"
+                f"Cannot store labware stack that does not correspond with the configuration of Flex Stacker in {location}"
             )
         if stacker_state.pool_lid_definition is not None:
             if labware_defs[-1] != stacker_state.pool_lid_definition:
                 raise CannotPerformModuleAction(
-                    "Cannot store labware stack that does not correspond with Flex Stacker configuration"
+                    f"Cannot store labware stack that does not correspond with the configuration of Flex Stacker in {location}"
                 )
             lid_id = labware_ids[-1]
 
@@ -154,14 +161,14 @@ def _verify_labware_to_store(
                 or labware_defs[1] != stacker_state.pool_primary_definition
             ):
                 raise CannotPerformModuleAction(
-                    "Cannot store labware stack that does not correspond with Flex Stacker configuration"
+                    f"Cannot store labware stack that does not correspond with the configuration of Flex Stacker in {location}"
                 )
             else:
                 return labware_ids[1], labware_ids[0], lid_id
         else:
             if labware_defs[0] != stacker_state.pool_primary_definition:
                 raise CannotPerformModuleAction(
-                    "Cannot store labware stack that does not correspond with Flex Stacker configuration"
+                    f"Cannot store labware stack that does not correspond with the configuration of Flex Stacker in {location}"
                 )
             return labware_ids[0], None, lid_id
 
@@ -170,16 +177,19 @@ async def execute(self, params: StoreParams) -> _ExecuteReturn:
         stacker_state = self._state_view.modules.get_flex_stacker_substate(
             params.moduleId
         )
-
-        if stacker_state.pool_count == stacker_state.max_pool_count:
+        location = self._state_view.modules.get_location(params.moduleId)
+        if (
+            len(stacker_state.contained_labware_bottom_first)
+            == stacker_state.max_pool_count
+        ):
             raise CannotPerformModuleAction(
-                "Cannot store labware in Flex Stacker while it is full"
+                f"Cannot store labware in Flex Stacker in {location} because it is full"
             )
 
         pool_definitions = stacker_state.get_pool_definition_ordered_list()
         if pool_definitions is None:
             raise FlexStackerLabwarePoolNotYetDefinedError(
-                message="The Flex Stacker has not been configured yet and cannot be filled."
+                message=f"The Flex Stacker in {location} has not been configured yet and cannot be filled."
             )
 
         primary_id, maybe_adapter_id, maybe_lid_id = self._verify_labware_to_store(
@@ -189,11 +199,6 @@ async def execute(self, params: StoreParams) -> _ExecuteReturn:
         # Allow propagation of ModuleNotAttachedError.
         stacker_hw = self._equipment.get_module_hardware_api(stacker_state.module_id)
 
-        eventual_target_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                InStackerHopperLocation(moduleId=params.moduleId)
-            )
-        )
         stack_height = self._state_view.geometry.get_height_of_labware_stack(
             pool_definitions
         )
@@ -235,6 +240,12 @@ async def execute(self, params: StoreParams) -> _ExecuteReturn:
             id for id in (primary_id, maybe_adapter_id, maybe_lid_id) if id is not None
         ]
 
+        group = StackerStoredLabwareGroup(
+            primaryLabwareId=primary_id,
+            adapterLabwareId=maybe_adapter_id,
+            lidLabwareId=maybe_lid_id,
+        )
+
         state_update.set_batch_labware_location(
             new_locations_by_id={
                 id: InStackerHopperLocation(moduleId=params.moduleId) for id in id_list
@@ -242,52 +253,50 @@ async def execute(self, params: StoreParams) -> _ExecuteReturn:
             new_offset_ids_by_id={id: None for id in id_list},
         )
 
-        state_update.update_flex_stacker_labware_pool_count(
-            module_id=params.moduleId, count=stacker_state.pool_count + 1
+        state_update.update_flex_stacker_contained_labware(
+            module_id=params.moduleId,
+            contained_labware_bottom_first=(
+                [group] + stacker_state.contained_labware_bottom_first
+            ),
+        )
+
+        original_location_sequences = labware_locations_for_group(
+            group,
+            labware_location_base_sequence(
+                group,
+                self._state_view,
+                self._state_view.geometry.get_predicted_location_sequence(
+                    ModuleLocation(moduleId=params.moduleId)
+                ),
+            ),
         )
-        if stacker_state.pool_primary_definition is None:
-            raise FlexStackerLabwarePoolNotYetDefinedError(
-                "The Primary Labware must be defined in the stacker pool."
-            )
 
         return SuccessData(
-            public=StoreResult(
-                eventualDestinationLocationSequence=eventual_target_location_sequence,
-                primaryOriginLocationSequence=self._state_view.geometry.get_location_sequence(
-                    primary_id
+            public=StoreResult.model_construct(
+                eventualDestinationLocationSequence=[
+                    InStackerHopperLocation(moduleId=params.moduleId)
+                ],
+                primaryOriginLocationSequence=primary_location_sequence(
+                    original_location_sequences
                 ),
                 primaryLabwareId=primary_id,
-                adapterOriginLocationSequence=(
-                    self._state_view.geometry.get_location_sequence(maybe_adapter_id)
-                    if maybe_adapter_id is not None
-                    else None
+                adapterOriginLocationSequence=adapter_location_sequence(
+                    original_location_sequences
                 ),
                 adapterLabwareId=maybe_adapter_id,
-                lidOriginLocationSequence=(
-                    self._state_view.geometry.get_location_sequence(maybe_lid_id)
-                    if maybe_lid_id is not None
-                    else None
+                lidOriginLocationSequence=lid_location_sequence(
+                    original_location_sequences
                 ),
                 lidLabwareId=maybe_lid_id,
-                primaryLabwareURI=uri_from_details(
-                    stacker_state.pool_primary_definition.namespace,
-                    stacker_state.pool_primary_definition.parameters.loadName,
-                    stacker_state.pool_primary_definition.version,
+                primaryLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    cast(LabwareDefinition, stacker_state.pool_primary_definition)
+                ),
+                adapterLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_adapter_definition
+                ),
+                lidLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_lid_definition
                 ),
-                adapterLabwareURI=uri_from_details(
-                    stacker_state.pool_adapter_definition.namespace,
-                    stacker_state.pool_adapter_definition.parameters.loadName,
-                    stacker_state.pool_adapter_definition.version,
-                )
-                if stacker_state.pool_adapter_definition is not None
-                else None,
-                lidLabwareURI=uri_from_details(
-                    stacker_state.pool_lid_definition.namespace,
-                    stacker_state.pool_lid_definition.parameters.loadName,
-                    stacker_state.pool_lid_definition.version,
-                )
-                if stacker_state.pool_lid_definition is not None
-                else None,
             ),
             state_update=state_update,
         )
diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_manual_retrieve.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_manual_retrieve.py
index 10d17791cfb..de117da722b 100644
--- a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_manual_retrieve.py
+++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_manual_retrieve.py
@@ -1,7 +1,7 @@
 """Command models to manually retrieve a labware from a Flex Stacker in an unsafe situation."""
 
 from __future__ import annotations
-from typing import Literal, TYPE_CHECKING, Any, Union, Optional
+from typing import Literal, TYPE_CHECKING, Any, Union, cast
 from typing_extensions import Type
 
 from pydantic import BaseModel, Field
@@ -14,7 +14,14 @@
     SuccessData,
     DefinedErrorData,
 )
-from ..flex_stacker.common import FlexStackerStallOrCollisionError
+from ..flex_stacker.common import (
+    FlexStackerStallOrCollisionError,
+    labware_locations_for_group,
+    build_retrieve_labware_move_updates,
+    primary_location_sequence,
+    adapter_location_sequence,
+    lid_location_sequence,
+)
 from ...errors import (
     ErrorOccurrence,
     CannotPerformModuleAction,
@@ -26,16 +33,13 @@
 from ...types import (
     ModuleLocation,
     LabwareLocationSequence,
-    LabwareLocation,
-    LoadedLabware,
+    InStackerHopperLocation,
 )
 from opentrons.hardware_control.modules.types import PlatformState
 from opentrons_shared_data.labware.labware_definition import LabwareDefinition
-from ..flex_stacker.retrieve import RetrieveResult
 
 if TYPE_CHECKING:
     from opentrons.protocol_engine.state.state import StateView
-    from opentrons.protocol_engine.state.module_substates import FlexStackerSubState
     from opentrons.protocol_engine.execution import EquipmentHandler
 
 UnsafeManualRetrieveCommandType = Literal["unsafe/manualRetrieve"]
@@ -54,31 +58,27 @@ class UnsafeManualRetrieveParams(BaseModel):
     )
     labwareId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to this labware. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     displayName: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional user-specified display name "
-        "or label for this labware.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     adapterId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to an adapter. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
     lidId: str | SkipJsonSchema[None] = Field(
         None,
-        description="An optional ID to assign to a lid. If None, an ID "
-        "will be generated.",
+        description="Do not use. Present for internal backward compatibility.",
         json_schema_extra=_remove_default,
     )
 
 
-class UnsafeManualRetrieveResult(RetrieveResult):
+class UnsafeManualRetrieveResult(BaseModel):
     """Result data from a labware retrieval command."""
 
     labwareId: str = Field(
@@ -103,6 +103,15 @@ class UnsafeManualRetrieveResult(RetrieveResult):
     adapterLocationSequence: LabwareLocationSequence | None = Field(
         None, description="The origin location of the lid labware on a primary labware."
     )
+    originalPrimaryLocationSequence: LabwareLocationSequence = Field(
+        ..., description="The original location of the just-retrieved primary labware"
+    )
+    originalAdapterLocationSequence: LabwareLocationSequence | None = Field(
+        None, description="The original location of an adapter labware if any"
+    )
+    originalLidLocationSequence: LabwareLocationSequence | None = Field(
+        None, description="The original location of a lid labware if any"
+    )
     primaryLabwareURI: str = Field(
         ...,
         description="The labware definition URI of the primary labware.",
@@ -139,127 +148,6 @@ def __init__(
         self._equipment = equipment
         self._model_utils = model_utils
 
-    async def _load_labware_from_pool(
-        self, params: UnsafeManualRetrieveParams, stacker_state: FlexStackerSubState
-    ) -> tuple[UnsafeManualRetrieveResult, update_types.StateUpdate]:
-        state_update = update_types.StateUpdate()
-
-        # Always load the primary labware
-        if stacker_state.pool_primary_definition is None:
-            raise CannotPerformModuleAction(
-                f"Flex Stacker {params.moduleId} has no labware to manually retrieve."
-            )
-
-        # Load the Stacker Pool Labwares
-        loaded_labware_pool = await self._equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=stacker_state.pool_primary_definition,
-            pool_adapter_definition=stacker_state.pool_adapter_definition,
-            pool_lid_definition=stacker_state.pool_lid_definition,
-            location=ModuleLocation(moduleId=params.moduleId),
-            primary_id=params.labwareId,
-            adapter_id=params.labwareId,
-            lid_id=params.lidId,
-        )
-
-        # Dictionaries for state updates and location sequencing
-        definitions_by_id: dict[str, LabwareDefinition] = {}
-        definitions_by_id[
-            loaded_labware_pool.primary_labware.id
-        ] = stacker_state.pool_primary_definition
-        offset_ids_by_id: dict[str, Optional[str]] = {}
-        display_names_by_id: dict[str, str | None] = {}
-        new_locations_by_id: dict[str, LabwareLocation] = {}
-        labware_by_id: dict[str, LoadedLabware] = {}
-
-        for loaded_labware in (
-            loaded_labware_pool.primary_labware,
-            loaded_labware_pool.adapter_labware,
-            loaded_labware_pool.lid_labware,
-        ):
-            if loaded_labware:
-                offset_ids_by_id[loaded_labware.id] = loaded_labware.offsetId
-                display_names_by_id[loaded_labware.id] = loaded_labware.displayName
-                new_locations_by_id[loaded_labware.id] = loaded_labware.location
-                labware_by_id[loaded_labware.id] = loaded_labware
-        if (
-            loaded_labware_pool.adapter_labware
-            and stacker_state.pool_adapter_definition is not None
-        ):
-            definitions_by_id[
-                loaded_labware_pool.adapter_labware.id
-            ] = stacker_state.pool_adapter_definition
-        if (
-            loaded_labware_pool.lid_labware
-            and stacker_state.pool_lid_definition is not None
-        ):
-            definitions_by_id[
-                loaded_labware_pool.lid_labware.id
-            ] = stacker_state.pool_lid_definition
-
-        # Get the labware dimensions for the labware being retrieved,
-        # which is the first one in the hopper labware id list
-        primary_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.primary_labware.id],
-                labware_by_id,
-            )
-        )
-        adapter_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.adapter_labware.id],
-                labware_by_id,
-            )
-            if loaded_labware_pool.adapter_labware is not None
-            else None
-        )
-        lid_location_sequence = (
-            self._state_view.geometry.get_predicted_location_sequence(
-                new_locations_by_id[loaded_labware_pool.lid_labware.id],
-                labware_by_id,
-            )
-            if loaded_labware_pool.lid_labware is not None
-            else None
-        )
-
-        state_update.set_batch_loaded_labware(
-            definitions_by_id=definitions_by_id,
-            display_names_by_id=display_names_by_id,
-            offset_ids_by_id=offset_ids_by_id,
-            new_locations_by_id=new_locations_by_id,
-        )
-        state_update.update_flex_stacker_labware_pool_count(
-            module_id=params.moduleId, count=stacker_state.pool_count - 1
-        )
-
-        if loaded_labware_pool.lid_labware is not None:
-            state_update.set_lids(
-                parent_labware_ids=[loaded_labware_pool.primary_labware.id],
-                lid_ids=[loaded_labware_pool.lid_labware.id],
-            )
-
-        return (
-            UnsafeManualRetrieveResult(
-                labwareId=loaded_labware_pool.primary_labware.id,
-                adapterId=loaded_labware_pool.adapter_labware.id
-                if loaded_labware_pool.adapter_labware is not None
-                else None,
-                lidId=loaded_labware_pool.lid_labware.id
-                if loaded_labware_pool.lid_labware is not None
-                else None,
-                primaryLocationSequence=primary_location_sequence,
-                adapterLocationSequence=adapter_location_sequence,
-                lidLocationSequence=lid_location_sequence,
-                primaryLabwareURI=loaded_labware_pool.primary_labware.definitionUri,
-                adapterLabwareURI=loaded_labware_pool.adapter_labware.definitionUri
-                if loaded_labware_pool.adapter_labware is not None
-                else None,
-                lidLabwareURI=loaded_labware_pool.lid_labware.definitionUri
-                if loaded_labware_pool.lid_labware is not None
-                else None,
-            ),
-            state_update,
-        )
-
     async def execute(self, params: UnsafeManualRetrieveParams) -> _ExecuteReturn:
         """Execute the labware retrieval command."""
         stacker_state = self._state_view.modules.get_flex_stacker_substate(
@@ -267,15 +155,15 @@ async def execute(self, params: UnsafeManualRetrieveParams) -> _ExecuteReturn:
         )
 
         pool_definitions = stacker_state.get_pool_definition_ordered_list()
+        location = self._state_view.modules.get_location(params.moduleId)
         if pool_definitions is None:
-            location = self._state_view.modules.get_location(params.moduleId)
             raise FlexStackerLabwarePoolNotYetDefinedError(
                 message=f"The Flex Stacker in {location} has not been configured yet and cannot be filled."
             )
 
-        if stacker_state.pool_count == 0:
+        if len(stacker_state.contained_labware_bottom_first) == 0:
             raise CannotPerformModuleAction(
-                message="Cannot retrieve labware from Flex Stacker because it contains no labware"
+                message=f"Cannot retrieve labware from Flex Stacker in {location} because it contains no labware"
             )
 
         stacker_loc = ModuleLocation(moduleId=params.moduleId)
@@ -288,7 +176,7 @@ async def execute(self, params: UnsafeManualRetrieveParams) -> _ExecuteReturn:
             and stacker_hw.platform_state != PlatformState.EXTENDED
         ):
             raise CannotPerformModuleAction(
-                "Cannot manually retrieve a labware from Flex Stacker if the carriage is not in gripper position."
+                f"Cannot manually retrieve a labware from Flex Stacker in {location} if the carriage is not in gripper position."
             )
 
         try:
@@ -296,11 +184,14 @@ async def execute(self, params: UnsafeManualRetrieveParams) -> _ExecuteReturn:
             self._state_view.labware.raise_if_labware_in_location(stacker_loc)
         except LocationIsOccupiedError:
             raise CannotPerformModuleAction(
-                "Cannot retrieve a labware from Flex Stacker if the carriage is occupied"
+                f"Cannot retrieve a labware from Flex Stacker in {location} if the carriage is occupied"
             )
 
-        retrieve_result, state_update = await self._load_labware_from_pool(
-            params, stacker_state
+        to_retrieve = stacker_state.contained_labware_bottom_first[0]
+        remaining = stacker_state.contained_labware_bottom_first[1:]
+
+        locations, offsets = build_retrieve_labware_move_updates(
+            to_retrieve, stacker_state, self._state_view
         )
 
         # Update the state to reflect the labware is now in the Flex Stacker slot
@@ -315,11 +206,53 @@ async def execute(self, params: UnsafeManualRetrieveParams) -> _ExecuteReturn:
                 model=self._state_view.modules.get(params.moduleId).model,
             )
         )
-        state_update.set_addressable_area_used(stacker_area)
+
+        original_locations = labware_locations_for_group(
+            to_retrieve,
+            self._state_view.geometry.get_predicted_location_sequence(
+                InStackerHopperLocation(moduleId=params.moduleId)
+            ),
+        )
+        new_locations = labware_locations_for_group(
+            to_retrieve,
+            self._state_view.geometry.get_predicted_location_sequence(
+                ModuleLocation(moduleId=params.moduleId)
+            ),
+        )
 
         return SuccessData(
-            public=retrieve_result,
-            state_update=state_update,
+            public=UnsafeManualRetrieveResult(
+                labwareId=to_retrieve.primaryLabwareId,
+                adapterId=to_retrieve.adapterLabwareId,
+                lidId=to_retrieve.lidLabwareId,
+                primaryLocationSequence=primary_location_sequence(new_locations),
+                adapterLocationSequence=adapter_location_sequence(new_locations),
+                lidLocationSequence=lid_location_sequence(new_locations),
+                originalPrimaryLocationSequence=primary_location_sequence(
+                    original_locations
+                ),
+                originalAdapterLocationSequence=adapter_location_sequence(
+                    original_locations
+                ),
+                originalLidLocationSequence=lid_location_sequence(original_locations),
+                primaryLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    cast(LabwareDefinition, stacker_state.pool_primary_definition)
+                ),
+                adapterLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_adapter_definition
+                ),
+                lidLabwareURI=self._state_view.labware.get_uri_from_definition_unless_none(
+                    stacker_state.pool_lid_definition
+                ),
+            ),
+            state_update=(
+                update_types.StateUpdate()
+                .set_batch_labware_location(
+                    new_locations_by_id=locations, new_offset_ids_by_id=offsets
+                )
+                .update_flex_stacker_contained_labware(params.moduleId, remaining)
+                .set_addressable_area_used(stacker_area)
+            ),
         )
 
 
diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py
index 5e3bfe12770..53f6778a26a 100644
--- a/api/src/opentrons/protocol_engine/state/labware.py
+++ b/api/src/opentrons/protocol_engine/state/labware.py
@@ -400,6 +400,10 @@ def get(self, labware_id: str) -> LoadedLabware:
                 f"Labware {labware_id} not found."
             ) from e
 
+    def known(self, labware_id: str) -> bool:
+        """Check if the labware specified by labware_id has been loaded."""
+        return labware_id in self._state.labware_by_id
+
     def get_id_by_module(self, module_id: str) -> str:
         """Return the ID of the labware loaded on the given module."""
         for labware_id, labware in self._state.labware_by_id.items():
@@ -808,6 +812,27 @@ def get_uri_from_definition(
             version=labware_definition.version,
         )
 
+    @overload
+    def get_uri_from_definition_unless_none(
+        self, labware_definition: LabwareDefinition
+    ) -> str:
+        ...
+
+    @overload
+    def get_uri_from_definition_unless_none(self, labware_definition: None) -> None:
+        ...
+
+    def get_uri_from_definition_unless_none(
+        self, labware_definition: LabwareDefinition | None
+    ) -> str | None:
+        """Get the URI from a labware definition, passing None through.
+
+        Don't use unless you're sure you want to accept that the definition might be None.
+        """
+        if labware_definition is None:
+            return None
+        return self.get_uri_from_definition(labware_definition)
+
     def is_tiprack(self, labware_id: str) -> bool:
         """Get whether labware is a tiprack."""
         definition = self.get_definition(labware_id)
diff --git a/api/src/opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py b/api/src/opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py
index 817d4fd4ffd..1f6878feb2f 100644
--- a/api/src/opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py
+++ b/api/src/opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py
@@ -3,6 +3,7 @@
 from dataclasses import dataclass
 from typing import NewType
 from opentrons_shared_data.labware.labware_definition import LabwareDefinition
+from opentrons.protocol_engine.types.module import StackerStoredLabwareGroup
 from opentrons.protocol_engine.state.update_types import (
     FlexStackerStateUpdate,
     NO_CHANGE,
@@ -24,9 +25,9 @@ class FlexStackerSubState:
     pool_primary_definition: LabwareDefinition | None
     pool_adapter_definition: LabwareDefinition | None
     pool_lid_definition: LabwareDefinition | None
-    pool_count: int
     max_pool_count: int
     pool_overlap: float
+    contained_labware_bottom_first: list[StackerStoredLabwareGroup]
 
     def new_from_state_change(
         self, update: FlexStackerStateUpdate
@@ -44,16 +45,17 @@ def new_from_state_change(
             pool_lid_definition = update.pool_constraint.lid_definition
             pool_overlap = update.pool_constraint.pool_overlap
 
-        pool_count = self.pool_count
-        if update.pool_count != NO_CHANGE:
-            pool_count = update.pool_count
+        contained_labware = self.contained_labware_bottom_first
+
+        if update.contained_labware_bottom_first != NO_CHANGE:
+            contained_labware = update.contained_labware_bottom_first
 
         return FlexStackerSubState(
             module_id=self.module_id,
             pool_primary_definition=pool_primary_definition,
             pool_adapter_definition=pool_adapter_definition,
             pool_lid_definition=pool_lid_definition,
-            pool_count=pool_count,
+            contained_labware_bottom_first=contained_labware,
             max_pool_count=max_pool_count,
             pool_overlap=pool_overlap,
         )
@@ -70,3 +72,7 @@ def get_pool_definition_ordered_list(self) -> list[LabwareDefinition] | None:
         if self.pool_adapter_definition is not None:
             defs.append(self.pool_adapter_definition)
         return defs
+
+    def get_contained_labware(self) -> list[StackerStoredLabwareGroup]:
+        """Get the labware inside the hopper."""
+        return self.contained_labware_bottom_first
diff --git a/api/src/opentrons/protocol_engine/state/modules.py b/api/src/opentrons/protocol_engine/state/modules.py
index f0118f810b1..49bded324cf 100644
--- a/api/src/opentrons/protocol_engine/state/modules.py
+++ b/api/src/opentrons/protocol_engine/state/modules.py
@@ -387,7 +387,7 @@ def _add_module_substate(
                 pool_primary_definition=None,
                 pool_adapter_definition=None,
                 pool_lid_definition=None,
-                pool_count=0,
+                contained_labware_bottom_first=[],
                 max_pool_count=0,
                 pool_overlap=0,
             )
diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py
index e2585dcdc1b..24b00d188a6 100644
--- a/api/src/opentrons/protocol_engine/state/update_types.py
+++ b/api/src/opentrons/protocol_engine/state/update_types.py
@@ -19,6 +19,7 @@
     LiquidClassRecord,
     ABSMeasureMode,
     LiquidTrackingType,
+    StackerStoredLabwareGroup,
 )
 from opentrons.types import MountType
 from opentrons_shared_data.labware.labware_definition import LabwareDefinition
@@ -379,7 +380,9 @@ class FlexStackerStateUpdate:
 
     module_id: str
     pool_constraint: FlexStackerPoolConstraint | NoChangeType = NO_CHANGE
-    pool_count: int | NoChangeType = NO_CHANGE
+    contained_labware_bottom_first: list[
+        StackerStoredLabwareGroup
+    ] | NoChangeType = NO_CHANGE
 
     @classmethod
     def create_or_override(
@@ -582,13 +585,13 @@ def set_labware_location(
     def set_batch_labware_location(
         self: Self,
         *,
-        new_locations_by_id: typing.Dict[str, LabwareLocation],
-        new_offset_ids_by_id: typing.Dict[str, str | None],
+        new_locations_by_id: typing.Mapping[str, LabwareLocation],
+        new_offset_ids_by_id: typing.Mapping[str, str | None],
     ) -> Self:
         """Update the location of multiple labware objects."""
         self.batch_labware_location = BatchLabwareLocationUpdate(
-            new_locations_by_id=new_locations_by_id,
-            new_offset_ids_by_id=new_offset_ids_by_id,
+            new_locations_by_id=dict(new_locations_by_id),
+            new_offset_ids_by_id=dict(new_offset_ids_by_id),
         )
         return self
 
@@ -612,17 +615,17 @@ def set_loaded_labware(
 
     def set_batch_loaded_labware(
         self: Self,
-        definitions_by_id: typing.Dict[str, LabwareDefinition],
-        offset_ids_by_id: typing.Dict[str, str | None],
-        display_names_by_id: typing.Dict[str, str | None],
-        new_locations_by_id: typing.Dict[str, LabwareLocation],
+        definitions_by_id: typing.Mapping[str, LabwareDefinition],
+        offset_ids_by_id: typing.Mapping[str, str | None],
+        display_names_by_id: typing.Mapping[str, str | None],
+        new_locations_by_id: typing.Mapping[str, LabwareLocation],
     ) -> Self:
         """Add a set of new labwares to state. See `BatchLoadedLabwareUpdate`."""
         self.batch_loaded_labware = BatchLoadedLabwareUpdate(
-            new_locations_by_id=new_locations_by_id,
-            offset_ids_by_id=offset_ids_by_id,
-            display_names_by_id=display_names_by_id,
-            definitions_by_id=definitions_by_id,
+            new_locations_by_id=dict(new_locations_by_id),
+            offset_ids_by_id=dict(offset_ids_by_id),
+            display_names_by_id=dict(display_names_by_id),
+            definitions_by_id=dict(definitions_by_id),
         )
         return self
 
@@ -646,13 +649,13 @@ def set_loaded_lid_stack(
 
     def set_lids(
         self: Self,
-        parent_labware_ids: typing.List[str],
-        lid_ids: typing.List[str | None],
+        parent_labware_ids: typing.Sequence[str],
+        lid_ids: typing.Sequence[str | None],
     ) -> Self:
         """Update the labware parent of a loaded or moved lid. See `LabwareLidUpdate`."""
         self.labware_lid = LabwareLidUpdate(
-            parent_labware_ids=parent_labware_ids,
-            lid_ids=lid_ids,
+            parent_labware_ids=list(parent_labware_ids),
+            lid_ids=list(lid_ids),
         )
         return self
 
@@ -855,15 +858,17 @@ def update_flex_stacker_labware_pool_definition(
         )
         return self
 
-    def update_flex_stacker_labware_pool_count(
-        self, module_id: str, count: int
+    def update_flex_stacker_contained_labware(
+        self,
+        module_id: str,
+        contained_labware_bottom_first: list[StackerStoredLabwareGroup],
     ) -> Self:
         """Set the labware pool to a specific count."""
         self.flex_stacker_state_update = dataclasses.replace(
             FlexStackerStateUpdate.create_or_override(
                 self.flex_stacker_state_update, module_id
             ),
-            pool_count=count,
+            contained_labware_bottom_first=contained_labware_bottom_first,
         )
         return self
 
diff --git a/api/src/opentrons/protocol_engine/types/__init__.py b/api/src/opentrons/protocol_engine/types/__init__.py
index 435d23dcca6..be60208e310 100644
--- a/api/src/opentrons/protocol_engine/types/__init__.py
+++ b/api/src/opentrons/protocol_engine/types/__init__.py
@@ -66,6 +66,7 @@
     ModuleOffsetVector,
     ModuleOffsetData,
     StackerFillEmptyStrategy,
+    StackerStoredLabwareGroup,
 )
 from .location import (
     DeckSlotLocation,
@@ -205,6 +206,7 @@
     "ModuleOffsetVector",
     "ModuleOffsetData",
     "StackerFillEmptyStrategy",
+    "StackerStoredLabwareGroup",
     # Locations of things on deck
     "DeckSlotLocation",
     "StagingSlotLocation",
diff --git a/api/src/opentrons/protocol_engine/types/module.py b/api/src/opentrons/protocol_engine/types/module.py
index e8a0035dace..b8ff68a5b5b 100644
--- a/api/src/opentrons/protocol_engine/types/module.py
+++ b/api/src/opentrons/protocol_engine/types/module.py
@@ -267,3 +267,11 @@ class StackerFillEmptyStrategy(str, Enum):
 
     MANUAL_WITH_PAUSE = "manualWithPause"
     LOGICAL = "logical"
+
+
+class StackerStoredLabwareGroup(BaseModel):
+    """Represents one group of labware stored in a stacker hopper."""
+
+    primaryLabwareId: str
+    adapterLabwareId: str | None
+    lidLabwareId: str | None
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_close_latch.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_close_latch.py
index 0bbe197c475..117c5bb6a02 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_close_latch.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_close_latch.py
@@ -44,7 +44,7 @@ async def test_close_latch_command(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=0,
         pool_overlap=0,
     )
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_common.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_common.py
new file mode 100644
index 00000000000..66314cfd785
--- /dev/null
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_common.py
@@ -0,0 +1,2232 @@
+"""Test the stacker coommon code."""
+
+from unittest.mock import sentinel
+
+import pytest
+from decoy import Decoy
+
+from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
+from opentrons_shared_data.labware.labware_definition import LabwareDefinition
+
+from opentrons.protocol_engine.types import (
+    StackerStoredLabwareGroup,
+    OnLabwareLocationSequenceComponent,
+    LabwareLocationSequence,
+    OFF_DECK_LOCATION,
+    ModuleLocation,
+    OnLabwareLocation,
+    LabwareUri,
+    InStackerHopperLocation,
+    LabwareLocation,
+    LoadedLabware,
+    OnLabwareOffsetLocationSequenceComponent,
+    LabwareOffset,
+)
+from opentrons.protocol_engine.execution.equipment import (
+    EquipmentHandler,
+    LoadedLabwarePoolData,
+)
+from opentrons.protocol_engine.resources import ModelUtils
+from opentrons.protocol_engine.commands.flex_stacker import common as subject
+from opentrons.protocol_engine.state.state import StateView
+from opentrons.protocol_engine.state.module_substates import (
+    FlexStackerSubState,
+    FlexStackerId,
+)
+from opentrons.protocol_engine.state.update_types import (
+    StateUpdate,
+    BatchLabwareLocationUpdate,
+    FlexStackerStateUpdate,
+    BatchLoadedLabwareUpdate,
+    LabwareLidUpdate,
+)
+
+
+@pytest.mark.parametrize(
+    "group_to_check,result",
+    [
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+            subject.GroupWithLocationSequences(
+                primary=subject.LabwareWithLocationSequence(
+                    labwareId="some-primary", locationSequence=[sentinel.base_location]
+                ),
+                adapter=None,
+                lid=None,
+            ),
+            id="primary-only",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId="some-adapter",
+                lidLabwareId=None,
+            ),
+            subject.GroupWithLocationSequences(
+                primary=subject.LabwareWithLocationSequence(
+                    labwareId="some-primary",
+                    locationSequence=[
+                        OnLabwareLocationSequenceComponent(
+                            labwareId="some-adapter", lidId=None
+                        ),
+                        sentinel.base_location,
+                    ],
+                ),
+                adapter=subject.LabwareWithLocationSequence(
+                    labwareId="some-adapter", locationSequence=[sentinel.base_location]
+                ),
+                lid=None,
+            ),
+            id="primary-and-adapter",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId=None,
+                lidLabwareId="some-lid",
+            ),
+            subject.GroupWithLocationSequences(
+                primary=subject.LabwareWithLocationSequence(
+                    labwareId="some-primary",
+                    locationSequence=[
+                        sentinel.base_location,
+                    ],
+                ),
+                adapter=None,
+                lid=subject.LabwareWithLocationSequence(
+                    labwareId="some-lid",
+                    locationSequence=[
+                        OnLabwareLocationSequenceComponent(
+                            labwareId="some-primary", lidId="some-lid"
+                        ),
+                        sentinel.base_location,
+                    ],
+                ),
+            ),
+            id="primary-and-lid",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId="some-adapter",
+                lidLabwareId="some-lid",
+            ),
+            subject.GroupWithLocationSequences(
+                primary=subject.LabwareWithLocationSequence(
+                    labwareId="some-primary",
+                    locationSequence=[
+                        OnLabwareLocationSequenceComponent(
+                            labwareId="some-adapter", lidId=None
+                        ),
+                        sentinel.base_location,
+                    ],
+                ),
+                adapter=subject.LabwareWithLocationSequence(
+                    labwareId="some-adapter", locationSequence=[sentinel.base_location]
+                ),
+                lid=subject.LabwareWithLocationSequence(
+                    labwareId="some-lid",
+                    locationSequence=[
+                        OnLabwareLocationSequenceComponent(
+                            labwareId="some-primary", lidId="some-lid"
+                        ),
+                        OnLabwareLocationSequenceComponent(
+                            labwareId="some-adapter", lidId=None
+                        ),
+                        sentinel.base_location,
+                    ],
+                ),
+            ),
+            id="primary-lid-and-adapter",
+        ),
+    ],
+)
+def test_labware_locations_for_group(
+    group_to_check: StackerStoredLabwareGroup,
+    result: subject.GroupWithLocationSequences,
+) -> None:
+    """It should bind locations to groups and fall back to None for missing groups."""
+    assert (
+        subject.labware_locations_for_group(group_to_check, [sentinel.base_location])
+        == result
+    )
+
+
+@pytest.mark.parametrize(
+    "group,result,known",
+    [
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+            [sentinel.primary_base],
+            True,
+            id="no-adapter-yes-known",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId="some-adapter",
+                lidLabwareId=None,
+            ),
+            [sentinel.adapter_base],
+            True,
+            id="yes-adapter-yes-known",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+            [sentinel.default],
+            False,
+            id="no-adapter-no-known",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="some-primary",
+                adapterLabwareId="some-adapter",
+                lidLabwareId=None,
+            ),
+            [sentinel.default],
+            False,
+            id="yes-adapter-no-known",
+        ),
+    ],
+)
+def test_labware_location_base_sequence(
+    group: StackerStoredLabwareGroup,
+    result: LabwareLocationSequence,
+    known: bool,
+    state_view: StateView,
+    decoy: Decoy,
+) -> None:
+    """It should query the correct labware's location."""
+    decoy.when(state_view.labware.known("some-primary")).then_return(known)
+    decoy.when(state_view.labware.known("some-adapter")).then_return(known)
+    decoy.when(state_view.geometry.get_location_sequence("some-primary")).then_return(
+        [sentinel.primary_base]
+    )
+    decoy.when(state_view.geometry.get_location_sequence("some-adapter")).then_return(
+        [sentinel.adapter_base]
+    )
+    assert (
+        subject.labware_location_base_sequence(group, state_view, [sentinel.default])
+        == result
+    )
+
+
+@pytest.mark.parametrize(
+    "groups,result",
+    [
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-1", locationSequence=[sentinel.first]
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-2", locationSequence=[sentinel.second]
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+            ],
+            [[sentinel.first], [sentinel.second]],
+            id="not-empty",
+        ),
+        pytest.param([], [], id="empty"),
+    ],
+)
+def test_primary_location_sequences(
+    groups: list[subject.GroupWithLocationSequences],
+    result: list[LabwareLocationSequence],
+) -> None:
+    """It should handle empty and non-empty lists."""
+    assert subject.primary_location_sequences(groups) == result
+
+
+@pytest.mark.parametrize(
+    "groups,result",
+    [
+        pytest.param([], [], id="empty-list"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-1",
+                        locationSequence=[sentinel.first_primary],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-2",
+                        locationSequence=[sentinel.second_primary],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+            ],
+            None,
+            id="no-adapter",
+        ),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-1",
+                        locationSequence=[sentinel.first_primary],
+                    ),
+                    adapter=subject.LabwareWithLocationSequence(
+                        labwareId="adapter-id-1",
+                        locationSequence=[sentinel.first_adapter],
+                    ),
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-2",
+                        locationSequence=[sentinel.second_primary],
+                    ),
+                    adapter=subject.LabwareWithLocationSequence(
+                        labwareId="adapter-id-2",
+                        locationSequence=[sentinel.second_adapter],
+                    ),
+                    lid=None,
+                ),
+            ],
+            [[sentinel.first_adapter], [sentinel.second_adapter]],
+            id="adapter",
+        ),
+    ],
+)
+def test_adapter_location_sequences(
+    groups: list[subject.GroupWithLocationSequences],
+    result: list[LabwareLocationSequence] | None,
+) -> None:
+    """It should handle lists, empty lists, and no-adapter cases."""
+    assert subject.adapter_location_sequences(groups) == result
+
+
+@pytest.mark.parametrize(
+    "groups,result,has_adapter",
+    [
+        pytest.param([], [], True, id="yes-adapter-empty"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-1",
+                        locationSequence=[sentinel.primary_location_1],
+                    ),
+                    adapter=subject.LabwareWithLocationSequence(
+                        labwareId="adapter-1",
+                        locationSequence=[sentinel.adapter_location_1],
+                    ),
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-2",
+                        locationSequence=[sentinel.primary_location_2],
+                    ),
+                    adapter=subject.LabwareWithLocationSequence(
+                        labwareId="adapter-2",
+                        locationSequence=[sentinel.adapter_location_2],
+                    ),
+                    lid=None,
+                ),
+            ],
+            [[sentinel.adapter_location_1], [sentinel.adapter_location_2]],
+            True,
+            id="yes-adapter",
+        ),
+        pytest.param([], None, False, id="no-adapter-empty"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-1",
+                        locationSequence=[sentinel.primary_location_1],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-2",
+                        locationSequence=[sentinel.primary_location_2],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+            ],
+            None,
+            False,
+            id="no-adapter",
+        ),
+    ],
+)
+def test_adapter_location_sequences_with_default(
+    groups: list[subject.GroupWithLocationSequences],
+    result: list[LabwareLocationSequence] | None,
+    has_adapter: bool,
+) -> None:
+    """It should handle cases where there is no adapter."""
+    assert (
+        subject.adapter_location_sequences_with_default(
+            groups, sentinel.adapter_def if has_adapter else None
+        )
+        == result
+    )
+
+
+@pytest.mark.parametrize(
+    "groups,result",
+    [
+        pytest.param([], [], id="empty-list"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-1",
+                        locationSequence=[sentinel.first_primary],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-2",
+                        locationSequence=[sentinel.second_primary],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+            ],
+            None,
+            id="no-lid",
+        ),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-1",
+                        locationSequence=[sentinel.first_primary],
+                    ),
+                    adapter=None,
+                    lid=subject.LabwareWithLocationSequence(
+                        labwareId="lid-id-1",
+                        locationSequence=[sentinel.first_lid],
+                    ),
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-id-2",
+                        locationSequence=[sentinel.second_primary],
+                    ),
+                    adapter=None,
+                    lid=subject.LabwareWithLocationSequence(
+                        labwareId="lid-id-2",
+                        locationSequence=[sentinel.second_lid],
+                    ),
+                ),
+            ],
+            [[sentinel.first_lid], [sentinel.second_lid]],
+            id="lid",
+        ),
+    ],
+)
+def test_lid_location_sequences(
+    groups: list[subject.GroupWithLocationSequences],
+    result: list[LabwareLocationSequence] | None,
+) -> None:
+    """It should handle lists, empty lists, and no-adapter cases."""
+    assert subject.lid_location_sequences(groups) == result
+
+
+@pytest.mark.parametrize(
+    "groups,result,has_lid",
+    [
+        pytest.param([], [], True, id="yes-lid-empty"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-1",
+                        locationSequence=[sentinel.primary_location_1],
+                    ),
+                    adapter=None,
+                    lid=subject.LabwareWithLocationSequence(
+                        labwareId="lid-1",
+                        locationSequence=[sentinel.lid_location_1],
+                    ),
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-2",
+                        locationSequence=[sentinel.primary_location_2],
+                    ),
+                    adapter=None,
+                    lid=subject.LabwareWithLocationSequence(
+                        labwareId="lid-2",
+                        locationSequence=[sentinel.lid_location_2],
+                    ),
+                ),
+            ],
+            [[sentinel.lid_location_1], [sentinel.lid_location_2]],
+            True,
+            id="yes-lid",
+        ),
+        pytest.param([], None, False, id="no-lid-empty"),
+        pytest.param(
+            [
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-1",
+                        locationSequence=[sentinel.primary_location_1],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+                subject.GroupWithLocationSequences(
+                    primary=subject.LabwareWithLocationSequence(
+                        labwareId="primary-2",
+                        locationSequence=[sentinel.primary_location_2],
+                    ),
+                    adapter=None,
+                    lid=None,
+                ),
+            ],
+            None,
+            False,
+            id="no-lid",
+        ),
+    ],
+)
+def test_lid_location_sequences_with_default(
+    groups: list[subject.GroupWithLocationSequences],
+    result: list[LabwareLocationSequence] | None,
+    has_lid: bool,
+) -> None:
+    """It should handle cases where there is no adapter."""
+    assert (
+        subject.lid_location_sequences_with_default(
+            groups, sentinel.lid_def if has_lid else None
+        )
+        == result
+    )
+
+
+@pytest.mark.parametrize("known", [True, False])
+@pytest.mark.parametrize(
+    "id_groups",
+    [
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    lidLabwareId=None,
+                    adapterLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    lidLabwareId=None,
+                    adapterLabwareId=None,
+                ),
+            ],
+            id="primaries-only",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    lidLabwareId="lid-1",
+                    adapterLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    lidLabwareId="lid-2",
+                    adapterLabwareId=None,
+                ),
+            ],
+            id="primaries-and-lids",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    lidLabwareId=None,
+                    adapterLabwareId="adapter-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    lidLabwareId=None,
+                    adapterLabwareId="adapter-2",
+                ),
+            ],
+            id="primaries-and-adapters",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    lidLabwareId="lid-1",
+                    adapterLabwareId="adapter-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    lidLabwareId="lid-2",
+                    adapterLabwareId="adapter-2",
+                ),
+            ],
+            id="primaries-adapters-and-lids",
+        ),
+        pytest.param([], id="empty-list"),
+    ],
+)
+def test_check_if_labware_preloaded(
+    state_view: StateView,
+    decoy: Decoy,
+    id_groups: list[StackerStoredLabwareGroup],
+    known: bool,
+) -> None:
+    """It should handle stack combinations."""
+    if len(id_groups) == 0 and known:
+        pytest.skip()
+    for group in id_groups:
+        decoy.when(state_view.labware.known(group.primaryLabwareId)).then_return(known)
+        if group.adapterLabwareId:
+            decoy.when(state_view.labware.known(group.adapterLabwareId)).then_return(
+                known
+            )
+        if group.lidLabwareId:
+            decoy.when(state_view.labware.known(group.lidLabwareId)).then_return(known)
+    assert (
+        subject.check_if_labware_preloaded(
+            ids=id_groups,
+            state_view=state_view,
+        )
+        is known
+    )
+
+
+def test_check_preloaded_labware_primary_consistency(
+    state_view: StateView,
+    decoy: Decoy,
+) -> None:
+    """It should check definitions are consistent between the pool and the loaded labware."""
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.primary_definition)
+    ).then_return(LabwareUri("primary-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.adapter_definition)
+    ).then_return(LabwareUri("adapter-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.lid_definition)
+    ).then_return(LabwareUri("lid-uri"))
+
+    decoy.when(state_view.labware.get_definition_uri("primary-id-1")).then_return(
+        LabwareUri("other-uri")
+    )
+
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="URI.*of primary labware.*must match pool URI",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            state_view,
+        )
+
+    decoy.when(state_view.labware.get_definition_uri("primary-id-1")).then_return(
+        LabwareUri("primary-uri")
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="No unspecified pool component may have an ID",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+    decoy.when(state_view.labware.get_location("primary-id-1")).then_return(
+        ModuleLocation(moduleId="some-id")
+    )
+
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="All existing labware.*must be currently OFF_DECK",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+        decoy.when(state_view.labware.get_location("primary-id-1")).then_return(
+            OFF_DECK_LOCATION
+        )
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            state_view,
+        )
+
+
+def test_check_preloaded_labware_adapter_consistency(
+    state_view: StateView,
+    decoy: Decoy,
+) -> None:
+    """It should check definitions are consistent between the pool and the loaded labware."""
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.primary_definition)
+    ).then_return(LabwareUri("primary-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.adapter_definition)
+    ).then_return(LabwareUri("adapter-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.lid_definition)
+    ).then_return(LabwareUri("lid-uri"))
+
+    decoy.when(state_view.labware.get_definition_uri("primary-id-1")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("primary-id-2")).then_return(
+        LabwareUri("primary-uri")
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="All pool components must have an ID, but adapter has no id",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            sentinel.adapter_definition,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+    decoy.when(state_view.labware.get_definition_uri("adapter-id-1")).then_return(
+        LabwareUri("other-uri")
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="URI.*of adapter labware.*must match pool URI",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            sentinel.adapter_definition,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+    decoy.when(state_view.labware.get_location("adapter-id-1")).then_return(
+        ModuleLocation(moduleId="some-module")
+    )
+    decoy.when(state_view.labware.get_definition_uri("adapter-id-1")).then_return(
+        LabwareUri("adapter-uri")
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="All existing adapters.*must be currently OFF_DECK",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            sentinel.adapter_definition,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+    decoy.when(state_view.labware.get_location("adapter-id-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(state_view.labware.get_location("primary-id-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="Existing labware groups.*must already be associated",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            sentinel.adapter_definition,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+    decoy.when(state_view.labware.get_location("primary-id-1")).then_return(
+        OnLabwareLocation(labwareId="adapter-id-1")
+    )
+    subject.check_preloaded_labware(
+        sentinel.primary_definition,
+        sentinel.adapter_definition,
+        None,
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id-1",
+                adapterLabwareId="adapter-id-1",
+                lidLabwareId=None,
+            )
+        ],
+        state_view,
+    )
+
+
+def test_check_preloaded_labware_lid_consistency(
+    state_view: StateView, decoy: Decoy
+) -> None:
+    """It should check for consistency of preloaded lids."""
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.primary_definition)
+    ).then_return(LabwareUri("primary-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.lid_definition)
+    ).then_return(LabwareUri("lid-uri"))
+
+    decoy.when(state_view.labware.get_definition_uri("primary-id-1")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_location("primary-id-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="All pool components must have an ID but lid has no id",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            sentinel.lid_definition,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            state_view,
+        )
+
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="No unspecified pool component may have an id, but lid has an id",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                )
+            ],
+            state_view,
+        )
+
+    decoy.when(state_view.labware.get_definition_uri("lid-id-1")).then_return(
+        LabwareUri("other-uri")
+    )
+    with pytest.raises(
+        CommandPreconditionViolated, match="URI.*of lid labware.*must match pool URI"
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            sentinel.lid_definition,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                )
+            ],
+            state_view,
+        )
+
+    decoy.when(state_view.labware.get_definition_uri("lid-id-1")).then_return(
+        LabwareUri("lid-uri")
+    )
+    decoy.when(state_view.labware.get_location("lid-id-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(state_view.labware.get_lid_id_by_labware_id("primary-id-1")).then_return(
+        "lid-id-1"
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="Existing labware groups.*must already be associated",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            sentinel.lid_definition,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                )
+            ],
+            state_view,
+        )
+
+    decoy.when(state_view.labware.get_location("lid-id-1")).then_return(
+        OnLabwareLocation(labwareId="primary-id-1")
+    )
+
+    decoy.when(state_view.labware.get_lid_id_by_labware_id("primary-id-1")).then_return(
+        None
+    )
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="Existing labware groups.*must already be associated",
+    ):
+        subject.check_preloaded_labware(
+            sentinel.primary_definition,
+            None,
+            sentinel.lid_definition,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                )
+            ],
+            state_view,
+        )
+
+    decoy.when(state_view.labware.get_location("lid-id-1")).then_return(
+        OnLabwareLocation(labwareId="primary-id-1")
+    )
+    decoy.when(state_view.labware.get_lid_id_by_labware_id("primary-id-1")).then_return(
+        "lid-id-1"
+    )
+    subject.check_preloaded_labware(
+        sentinel.primary_definition,
+        None,
+        sentinel.lid_definition,
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id-1",
+                adapterLabwareId=None,
+                lidLabwareId="lid-id-1",
+            )
+        ],
+        state_view,
+    )
+
+
+async def test_assign_n_labware_happypath(state_view: StateView, decoy: Decoy) -> None:
+    """When all conditions are met, it should assign labware using state updates."""
+    decoy.when(state_view.labware.known("primary-1")).then_return(True)
+    decoy.when(state_view.labware.known("primary-2")).then_return(True)
+    decoy.when(state_view.labware.known("adapter-1")).then_return(True)
+    decoy.when(state_view.labware.known("adapter-2")).then_return(True)
+    decoy.when(state_view.labware.known("lid-1")).then_return(True)
+    decoy.when(state_view.labware.known("lid-2")).then_return(True)
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.primary_definition)
+    ).then_return(LabwareUri("primary-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.adapter_definition)
+    ).then_return(LabwareUri("adapter-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.lid_definition)
+    ).then_return(LabwareUri("lid-uri"))
+    decoy.when(state_view.labware.get_definition_uri("primary-1")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("primary-2")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("adapter-1")).then_return(
+        LabwareUri("adapter-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("adapter-2")).then_return(
+        LabwareUri("adapter-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("lid-1")).then_return(
+        LabwareUri("lid-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("lid-2")).then_return(
+        LabwareUri("lid-uri")
+    )
+    decoy.when(state_view.labware.get_location("primary-1")).then_return(
+        OnLabwareLocation(labwareId="adapter-1")
+    )
+    decoy.when(state_view.labware.get_location("primary-2")).then_return(
+        OnLabwareLocation(labwareId="adapter-2")
+    )
+    decoy.when(state_view.labware.get_location("adapter-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(state_view.labware.get_location("adapter-2")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(state_view.labware.get_location("lid-1")).then_return(
+        OnLabwareLocation(labwareId="primary-1")
+    )
+    decoy.when(state_view.labware.get_location("lid-2")).then_return(
+        OnLabwareLocation(labwareId="primary-2")
+    )
+    decoy.when(state_view.labware.get_lid_id_by_labware_id("primary-1")).then_return(
+        "lid-1"
+    )
+    decoy.when(state_view.labware.get_lid_id_by_labware_id("primary-2")).then_return(
+        "lid-2"
+    )
+    state, stored = await subject.assign_n_labware(
+        sentinel.primary_definition,
+        sentinel.adapter_definition,
+        sentinel.lid_definition,
+        "module-id",
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-1",
+                adapterLabwareId="adapter-1",
+                lidLabwareId="lid-1",
+            ),
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-2",
+                adapterLabwareId="adapter-2",
+                lidLabwareId="lid-2",
+            ),
+        ],
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-pre-1",
+                adapterLabwareId="adapter-pre-1",
+                lidLabwareId="lid-pre-1",
+            ),
+        ],
+        state_view,
+    )
+
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=[
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId="adapter-pre-1",
+                    lidLabwareId="lid-pre-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    adapterLabwareId="adapter-1",
+                    lidLabwareId="lid-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    adapterLabwareId="adapter-2",
+                    lidLabwareId="lid-2",
+                ),
+            ],
+        ),
+        batch_labware_location=BatchLabwareLocationUpdate(
+            new_locations_by_id={
+                "adapter-1": InStackerHopperLocation(moduleId="module-id"),
+                "adapter-2": InStackerHopperLocation(moduleId="module-id"),
+            },
+            new_offset_ids_by_id={
+                "adapter-1": None,
+                "adapter-2": None,
+                "primary-1": None,
+                "primary-2": None,
+                "lid-1": None,
+                "lid-2": None,
+            },
+        ),
+    )
+
+    assert stored == [
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-pre-1",
+            adapterLabwareId="adapter-pre-1",
+            lidLabwareId="lid-pre-1",
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-1",
+            adapterLabwareId="adapter-1",
+            lidLabwareId="lid-1",
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-2",
+            adapterLabwareId="adapter-2",
+            lidLabwareId="lid-2",
+        ),
+    ]
+
+
+async def test_assign_n_labware_no_offsets_for_unspecified(
+    state_view: StateView, decoy: Decoy
+) -> None:
+    """When all conditions are met, it should assign labware using state updates."""
+    decoy.when(state_view.labware.known("primary-1")).then_return(True)
+    decoy.when(state_view.labware.known("primary-2")).then_return(True)
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.primary_definition)
+    ).then_return(LabwareUri("primary-uri"))
+    decoy.when(state_view.labware.get_definition_uri("primary-1")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_definition_uri("primary-2")).then_return(
+        LabwareUri("primary-uri")
+    )
+    decoy.when(state_view.labware.get_location("primary-1")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(state_view.labware.get_location("primary-2")).then_return(
+        OFF_DECK_LOCATION
+    )
+    state, stored = await subject.assign_n_labware(
+        sentinel.primary_definition,
+        None,
+        None,
+        "module-id",
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-1",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-2",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+        ],
+        [
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-pre-1",
+                adapterLabwareId=None,
+                lidLabwareId=None,
+            ),
+        ],
+        state_view,
+    )
+
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=[
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+        ),
+        batch_labware_location=BatchLabwareLocationUpdate(
+            new_locations_by_id={
+                "primary-1": InStackerHopperLocation(moduleId="module-id"),
+                "primary-2": InStackerHopperLocation(moduleId="module-id"),
+            },
+            new_offset_ids_by_id={
+                "primary-1": None,
+                "primary-2": None,
+            },
+        ),
+    )
+
+    assert stored == [
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-pre-1",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-1",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="primary-2",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        ),
+    ]
+
+
+@pytest.mark.parametrize(
+    "ids,current_contains",
+    [
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="has-contents",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            [],
+            id="no-contents",
+        ),
+    ],
+)
+async def test_build_n_labware_happypath_primary_only(
+    ids: list[StackerStoredLabwareGroup],
+    current_contains: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+    decoy: Decoy,
+) -> None:
+    """It should build the specified labware."""
+    for id_group in ids:
+        decoy.when(
+            await equipment.load_labware_pool_from_definitions(
+                pool_primary_definition=sentinel.primary_labware_def,
+                pool_adapter_definition=None,
+                pool_lid_definition=None,
+                location=InStackerHopperLocation(moduleId="module-id"),
+                primary_id=id_group.primaryLabwareId,
+                adapter_id=None,
+                lid_id=None,
+            )
+        ).then_return(
+            LoadedLabwarePoolData(
+                primary_labware=LoadedLabware(
+                    id=id_group.primaryLabwareId,
+                    loadName="some-load-name",
+                    definitionUri="some-uri",
+                    location=InStackerHopperLocation(moduleId="module-id"),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                adapter_labware=None,
+                lid_labware=None,
+            )
+        )
+    state, stored = await subject.build_n_labware_with_ids(
+        pool_primary_definition=sentinel.primary_labware_def,
+        pool_adapter_definition=None,
+        pool_lid_definition=None,
+        module_id="module-id",
+        ids=ids,
+        current_contained_labware=current_contains,
+        equipment=equipment,
+    )
+    assert stored == current_contains + ids
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=current_contains + ids,
+        ),
+        batch_loaded_labware=BatchLoadedLabwareUpdate(
+            new_locations_by_id={
+                group.primaryLabwareId: InStackerHopperLocation(moduleId="module-id")
+                for group in ids
+            },
+            offset_ids_by_id={group.primaryLabwareId: None for group in ids},
+            display_names_by_id={group.primaryLabwareId: None for group in ids},
+            definitions_by_id={
+                group.primaryLabwareId: sentinel.primary_labware_def for group in ids
+            },
+        ),
+        labware_lid=LabwareLidUpdate(parent_labware_ids=[], lid_ids=[]),
+    )
+
+
+@pytest.mark.parametrize(
+    "ids,current_contains",
+    [
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId=None,
+                ),
+            ],
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId="adapter-pre-1",
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-2",
+                    adapterLabwareId="adapter-pre-2",
+                    lidLabwareId=None,
+                ),
+            ],
+            id="has-contents",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId=None,
+                ),
+            ],
+            [],
+            id="no-contents",
+        ),
+    ],
+)
+async def test_build_n_labware_happypath_primary_and_adapter(
+    ids: list[StackerStoredLabwareGroup],
+    current_contains: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+    decoy: Decoy,
+) -> None:
+    """It should build the specified labware."""
+    for id_group in ids:
+        assert id_group.adapterLabwareId
+        decoy.when(
+            await equipment.load_labware_pool_from_definitions(
+                pool_primary_definition=sentinel.primary_labware_def,
+                pool_adapter_definition=sentinel.adapter_labware_def,
+                pool_lid_definition=None,
+                location=InStackerHopperLocation(moduleId="module-id"),
+                primary_id=id_group.primaryLabwareId,
+                adapter_id=id_group.adapterLabwareId,
+                lid_id=None,
+            )
+        ).then_return(
+            LoadedLabwarePoolData(
+                primary_labware=LoadedLabware(
+                    id=id_group.primaryLabwareId,
+                    loadName="some-load-name",
+                    definitionUri="some-uri",
+                    location=OnLabwareLocation(labwareId=id_group.adapterLabwareId),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                adapter_labware=LoadedLabware(
+                    id=id_group.adapterLabwareId,
+                    loadName="adapter-load-name",
+                    definitionUri="adapter-uri",
+                    location=InStackerHopperLocation(
+                        moduleId="module-id",
+                    ),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                lid_labware=None,
+            )
+        )
+    state, stored = await subject.build_n_labware_with_ids(
+        pool_primary_definition=sentinel.primary_labware_def,
+        pool_adapter_definition=sentinel.adapter_labware_def,
+        pool_lid_definition=None,
+        module_id="module-id",
+        ids=ids,
+        current_contained_labware=current_contains,
+        equipment=equipment,
+    )
+    assert stored == current_contains + ids
+    location_ids: dict[str, LabwareLocation] = {}
+    ids_flat: list[str] = []
+    defs: dict[str, LabwareDefinition] = {}
+    for group in ids:
+        assert group.adapterLabwareId
+        location_ids[group.primaryLabwareId] = OnLabwareLocation(
+            labwareId=group.adapterLabwareId
+        )
+        location_ids[group.adapterLabwareId] = InStackerHopperLocation(
+            moduleId="module-id"
+        )
+        ids_flat.extend([group.primaryLabwareId, group.adapterLabwareId])
+        defs[group.primaryLabwareId] = sentinel.primary_labware_def
+        defs[group.adapterLabwareId] = sentinel.adapter_labware_def
+
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=current_contains + ids,
+        ),
+        batch_loaded_labware=BatchLoadedLabwareUpdate(
+            new_locations_by_id=location_ids,
+            offset_ids_by_id={id: None for id in ids_flat},
+            display_names_by_id={id: None for id in ids_flat},
+            definitions_by_id=defs,
+        ),
+        labware_lid=LabwareLidUpdate(parent_labware_ids=[], lid_ids=[]),
+    )
+
+
+@pytest.mark.parametrize(
+    "ids,current_contains",
+    [
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-2",
+                ),
+            ],
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-pre-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-2",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-pre-2",
+                ),
+            ],
+            id="has-contents",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-2",
+                ),
+            ],
+            [],
+            id="no-contents",
+        ),
+    ],
+)
+async def test_build_n_labware_happypath_primary_and_lid(
+    ids: list[StackerStoredLabwareGroup],
+    current_contains: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+    decoy: Decoy,
+) -> None:
+    """It should build the specified labware."""
+    for id_group in ids:
+        assert id_group.lidLabwareId
+        decoy.when(
+            await equipment.load_labware_pool_from_definitions(
+                pool_primary_definition=sentinel.primary_labware_def,
+                pool_adapter_definition=None,
+                pool_lid_definition=sentinel.lid_labware_def,
+                location=InStackerHopperLocation(moduleId="module-id"),
+                primary_id=id_group.primaryLabwareId,
+                adapter_id=None,
+                lid_id=id_group.lidLabwareId,
+            )
+        ).then_return(
+            LoadedLabwarePoolData(
+                primary_labware=LoadedLabware(
+                    id=id_group.primaryLabwareId,
+                    loadName="some-load-name",
+                    definitionUri="some-uri",
+                    location=InStackerHopperLocation(
+                        moduleId="module-id",
+                    ),
+                    lid_id=id_group.lidLabwareId,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                adapter_labware=None,
+                lid_labware=LoadedLabware(
+                    id=id_group.lidLabwareId,
+                    loadName="lid-load-name",
+                    definitionUri="lid-uri",
+                    location=OnLabwareLocation(
+                        labwareId=id_group.primaryLabwareId,
+                    ),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+            )
+        )
+    state, stored = await subject.build_n_labware_with_ids(
+        pool_primary_definition=sentinel.primary_labware_def,
+        pool_adapter_definition=None,
+        pool_lid_definition=sentinel.lid_labware_def,
+        module_id="module-id",
+        ids=ids,
+        current_contained_labware=current_contains,
+        equipment=equipment,
+    )
+    assert stored == current_contains + ids
+    location_ids: dict[str, LabwareLocation] = {}
+    ids_flat: list[str] = []
+    defs: dict[str, LabwareDefinition] = {}
+    for group in ids:
+        assert group.lidLabwareId
+        location_ids[group.primaryLabwareId] = InStackerHopperLocation(
+            moduleId="module-id"
+        )
+        location_ids[group.lidLabwareId] = OnLabwareLocation(
+            labwareId=group.primaryLabwareId
+        )
+        ids_flat.extend([group.primaryLabwareId, group.lidLabwareId])
+        defs[group.primaryLabwareId] = sentinel.primary_labware_def
+        defs[group.lidLabwareId] = sentinel.lid_labware_def
+
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=current_contains + ids,
+        ),
+        batch_loaded_labware=BatchLoadedLabwareUpdate(
+            new_locations_by_id=location_ids,
+            offset_ids_by_id={id: None for id in ids_flat},
+            display_names_by_id={id: None for id in ids_flat},
+            definitions_by_id=defs,
+        ),
+        labware_lid=LabwareLidUpdate(
+            parent_labware_ids=[id_group.primaryLabwareId for id_group in ids],
+            lid_ids=[id_group.lidLabwareId for id_group in ids],
+        ),
+    )
+
+
+@pytest.mark.parametrize(
+    "ids,current_contains",
+    [
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId="lid-id-2",
+                ),
+            ],
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-1",
+                    adapterLabwareId="adapter-pre-1",
+                    lidLabwareId="lid-pre-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-pre-2",
+                    adapterLabwareId="adapter-pre-2",
+                    lidLabwareId="lid-pre-2",
+                ),
+            ],
+            id="has-contents",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId="lid-id-2",
+                ),
+            ],
+            [],
+            id="no-contents",
+        ),
+    ],
+)
+async def test_build_n_labware_happypath_primary_and_lid_and_adapter(
+    ids: list[StackerStoredLabwareGroup],
+    current_contains: list[StackerStoredLabwareGroup],
+    equipment: EquipmentHandler,
+    decoy: Decoy,
+) -> None:
+    """It should build the specified labware."""
+    for id_group in ids:
+        assert id_group.lidLabwareId
+        assert id_group.adapterLabwareId
+        decoy.when(
+            await equipment.load_labware_pool_from_definitions(
+                pool_primary_definition=sentinel.primary_labware_def,
+                pool_adapter_definition=sentinel.adapter_labware_def,
+                pool_lid_definition=sentinel.lid_labware_def,
+                location=InStackerHopperLocation(moduleId="module-id"),
+                primary_id=id_group.primaryLabwareId,
+                adapter_id=id_group.adapterLabwareId,
+                lid_id=id_group.lidLabwareId,
+            )
+        ).then_return(
+            LoadedLabwarePoolData(
+                primary_labware=LoadedLabware(
+                    id=id_group.primaryLabwareId,
+                    loadName="some-load-name",
+                    definitionUri="some-uri",
+                    location=OnLabwareLocation(labwareId=id_group.adapterLabwareId),
+                    lid_id=id_group.lidLabwareId,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                adapter_labware=LoadedLabware(
+                    id=id_group.adapterLabwareId,
+                    loadName="adapter-load-name",
+                    definitionUri="adapter-uri",
+                    location=InStackerHopperLocation(
+                        moduleId="module-id",
+                    ),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                lid_labware=LoadedLabware(
+                    id=id_group.lidLabwareId,
+                    loadName="lid-load-name",
+                    definitionUri="lid-uri",
+                    location=OnLabwareLocation(
+                        labwareId=id_group.primaryLabwareId,
+                    ),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+            )
+        )
+    state, stored = await subject.build_n_labware_with_ids(
+        pool_primary_definition=sentinel.primary_labware_def,
+        pool_adapter_definition=sentinel.adapter_labware_def,
+        pool_lid_definition=sentinel.lid_labware_def,
+        module_id="module-id",
+        ids=ids,
+        current_contained_labware=current_contains,
+        equipment=equipment,
+    )
+    assert stored == current_contains + ids
+    location_ids: dict[str, LabwareLocation] = {}
+    ids_flat: list[str] = []
+    defs: dict[str, LabwareDefinition] = {}
+    for group in ids:
+        assert group.lidLabwareId
+        assert group.adapterLabwareId
+        location_ids[group.primaryLabwareId] = OnLabwareLocation(
+            labwareId=group.adapterLabwareId
+        )
+        location_ids[group.adapterLabwareId] = InStackerHopperLocation(
+            moduleId="module-id"
+        )
+        location_ids[group.lidLabwareId] = OnLabwareLocation(
+            labwareId=group.primaryLabwareId
+        )
+        ids_flat.extend(
+            [group.primaryLabwareId, group.lidLabwareId, group.adapterLabwareId]
+        )
+        defs[group.primaryLabwareId] = sentinel.primary_labware_def
+        defs[group.lidLabwareId] = sentinel.lid_labware_def
+        defs[group.adapterLabwareId] = sentinel.adapter_labware_def
+
+    assert state == StateUpdate(
+        flex_stacker_state_update=FlexStackerStateUpdate(
+            module_id="module-id",
+            contained_labware_bottom_first=current_contains + ids,
+        ),
+        batch_loaded_labware=BatchLoadedLabwareUpdate(
+            new_locations_by_id=location_ids,
+            offset_ids_by_id={id: None for id in ids_flat},
+            display_names_by_id={id: None for id in ids_flat},
+            definitions_by_id=defs,
+        ),
+        labware_lid=LabwareLidUpdate(
+            parent_labware_ids=[id_group.primaryLabwareId for id_group in ids],
+            lid_ids=[id_group.lidLabwareId for id_group in ids],
+        ),
+    )
+
+
+@pytest.mark.parametrize(
+    "initial_labware,initial_count,current_count,results",
+    [
+        pytest.param(
+            None,
+            3,
+            0,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="generate-all-from-empty",
+        ),
+        pytest.param(
+            None,
+            3,
+            2,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="generate-all-from-nonempty",
+        ),
+        pytest.param(
+            None,
+            4,
+            0,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="generate-too-many-from-empty",
+        ),
+        pytest.param(
+            None,
+            4,
+            2,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="generated-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="generate-too-many-from-nonempty",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            None,
+            0,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="specify-all-from-empty",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            None,
+            2,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            id="specify-all-from-nonempty",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            3,
+            0,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            id="specify-all-with-count-from-empty",
+        ),
+        pytest.param(
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            1,
+            2,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="specified-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            id="specify-all-with-count-from-nonempty",
+        ),
+    ],
+)
+def test_build_ids_to_fill_variants(
+    initial_labware: list[StackerStoredLabwareGroup] | None,
+    initial_count: int | None,
+    current_count: int,
+    results: list[StackerStoredLabwareGroup],
+    model_utils: ModelUtils,
+    decoy: Decoy,
+) -> None:
+    """It should build ids appropriately."""
+    max_count = 3
+    generated_id_counts = 0
+
+    def _new_id() -> str:
+        nonlocal generated_id_counts
+        generated_id_counts += 1
+        return f"generated-{generated_id_counts}"
+
+    decoy.when(model_utils.generate_id()).then_do(_new_id)
+
+    assert (
+        subject.build_ids_to_fill(
+            False,
+            False,
+            initial_labware,
+            initial_count,
+            max_count,
+            current_count,
+            model_utils,
+        )
+        == results
+    )
+
+
+@pytest.mark.parametrize(
+    "specified_count,current_count",
+    [
+        pytest.param(
+            3,
+            0,
+            id="empty",
+        ),
+        pytest.param(
+            2,
+            1,
+            id="nonempty",
+        ),
+    ],
+)
+def test_build_ids_to_fill_fails_on_too_many_specified(
+    specified_count: int,
+    current_count: int,
+    model_utils: ModelUtils,
+) -> None:
+    """It should prevent you from specifying too many labware."""
+    specified = [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{idx}",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        )
+        for idx in range(specified_count)
+    ]
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match=".*were requested to be stored, but the stacker can store only.*",
+    ):
+        subject.build_ids_to_fill(
+            False,
+            False,
+            specified,
+            None,
+            2,
+            current_count,
+            model_utils,
+        )
+
+
+def test_build_ids_to_fill_fails_on_mismatched_count(model_utils: ModelUtils) -> None:
+    """It should prevent you from specifying too many labware."""
+    with pytest.raises(
+        CommandPreconditionViolated,
+        match="If initialCount and initialStoredLabware are both specified,.*",
+    ):
+        subject.build_ids_to_fill(
+            False,
+            False,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="primary-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            2,
+            2,
+            0,
+            model_utils,
+        )
+
+
+@pytest.mark.parametrize("has_adapter", [True, False])
+@pytest.mark.parametrize("has_lid", [True, False])
+def test_build_ids_to_fill_builds_specified_components(
+    has_adapter: bool, has_lid: bool, model_utils: ModelUtils, decoy: Decoy
+) -> None:
+    """It should build adapter and labware ids only where necessary."""
+    generated_id_counts = 0
+
+    def _new_id() -> str:
+        nonlocal generated_id_counts
+        generated_id_counts += 1
+        return f"generated-{generated_id_counts}"
+
+    decoy.when(model_utils.generate_id()).then_do(_new_id)
+    subject.build_ids_to_fill(has_adapter, has_lid, None, 1, 2, 0, model_utils,) == [
+        StackerStoredLabwareGroup(
+            primaryLabwareId="generated-1",
+            adapterLabwareId="generated-2" if has_adapter else None,
+            lidLabwareId="generated-3"
+            if has_adapter and has_lid
+            else ("generated-2" if has_lid else None),
+        )
+    ]
+
+
+@pytest.mark.parametrize(
+    "group,locations,offsets",
+    [
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id", adapterLabwareId=None, lidLabwareId=None
+            ),
+            {"primary-id": ModuleLocation(moduleId="stacker-id")},
+            {"primary-id": "primary-base-id"},
+            id="primary-only",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id",
+                adapterLabwareId="adapter-id",
+                lidLabwareId=None,
+            ),
+            {
+                "adapter-id": ModuleLocation(moduleId="stacker-id"),
+            },
+            {
+                "primary-id": "primary-on-adapter-base-id",
+                "adapter-id": "adapter-base-id",
+            },
+            id="primary-and-adapter",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id",
+                adapterLabwareId=None,
+                lidLabwareId="lid-id",
+            ),
+            {
+                "primary-id": ModuleLocation(moduleId="stacker-id"),
+            },
+            {"primary-id": "primary-base-id", "lid-id": "lid-primary-base-id"},
+            id="primary-and-lid",
+        ),
+        pytest.param(
+            StackerStoredLabwareGroup(
+                primaryLabwareId="primary-id",
+                adapterLabwareId="adapter-id",
+                lidLabwareId="lid-id",
+            ),
+            {
+                "adapter-id": ModuleLocation(moduleId="stacker-id"),
+            },
+            {
+                "primary-id": "primary-on-adapter-base-id",
+                "adapter-id": "adapter-base-id",
+                "lid-id": "lid-on-primary-on-adapter-base-id",
+            },
+            id="primary-adapter-and-lid",
+        ),
+    ],
+)
+def test_build_retrieve_labware_move_updates(
+    group: StackerStoredLabwareGroup,
+    locations: dict[str, LabwareLocation],
+    offsets: dict[str, None],
+    state_view: StateView,
+    decoy: Decoy,
+) -> None:
+    """It should build appropriate data for batch labware location."""
+    stacker = FlexStackerSubState(
+        module_id=FlexStackerId("stacker-id"),
+        pool_primary_definition=sentinel.pool_primary_definition,
+        pool_adapter_definition=sentinel.pool_adapter_definition,
+        pool_lid_definition=sentinel.pool_lid_definition,
+        max_pool_count=3,
+        pool_overlap=1,
+        contained_labware_bottom_first=[group],
+    )
+    decoy.when(
+        state_view.geometry.get_projected_offset_location(
+            ModuleLocation(moduleId="stacker-id")
+        )
+    ).then_return([sentinel.offset_location_base])
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "adapter-labware-uri", [sentinel.offset_location_base]
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="adapter-base-id")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "primary-labware-uri", [sentinel.offset_location_base]
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="primary-base-id")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "primary-labware-uri",
+            [
+                OnLabwareOffsetLocationSequenceComponent.model_construct(
+                    labwareUri="adapter-labware-uri",
+                ),
+                sentinel.offset_location_base,
+            ],
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="primary-on-adapter-base-id")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "lid-labware-uri",
+            [
+                OnLabwareOffsetLocationSequenceComponent.model_construct(
+                    labwareUri="primary-labware-uri"
+                ),
+                sentinel.offset_location_base,
+            ],
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="lid-primary-base-id")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "lid-labware-uri",
+            [
+                OnLabwareOffsetLocationSequenceComponent.model_construct(
+                    labwareUri="primary-labware-uri"
+                ),
+                OnLabwareOffsetLocationSequenceComponent.model_construct(
+                    labwareUri="adapter-labware-uri"
+                ),
+                sentinel.offset_location_base,
+            ],
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="lid-on-primary-on-adapter-base-id")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.pool_primary_definition)
+    ).then_return(LabwareUri("primary-labware-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.pool_adapter_definition)
+    ).then_return(LabwareUri("adapter-labware-uri"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(sentinel.pool_lid_definition)
+    ).then_return(LabwareUri("lid-labware-uri"))
+    check_locations, check_offsets = subject.build_retrieve_labware_move_updates(
+        group, stacker, state_view
+    )
+    assert check_locations == locations
+    assert check_offsets == offsets
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_empty.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_empty.py
index d9f30c6a237..78de89a6f3a 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_empty.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_empty.py
@@ -7,6 +7,7 @@
 from opentrons.protocol_engine.state.update_types import (
     StateUpdate,
     FlexStackerStateUpdate,
+    BatchLabwareLocationUpdate,
 )
 
 from opentrons.protocol_engine.state.state import StateView
@@ -20,7 +21,15 @@
     EmptyParams,
     EmptyResult,
 )
-from opentrons.protocol_engine.types import StackerFillEmptyStrategy, DeckSlotLocation
+from opentrons.protocol_engine.types import (
+    StackerFillEmptyStrategy,
+    DeckSlotLocation,
+    StackerStoredLabwareGroup,
+    NotOnDeckLocationSequenceComponent,
+    OFF_DECK_LOCATION,
+    InStackerHopperLocation,
+    LabwareUri,
+)
 from opentrons.protocol_engine.errors import (
     ModuleNotLoadedError,
     FlexStackerLabwarePoolNotYetDefinedError,
@@ -29,6 +38,17 @@
 from opentrons.types import DeckSlotName
 
 
+def _contained_labware(count: int) -> list[StackerStoredLabwareGroup]:
+    return [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{i+1}",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        )
+        for i in range(count)
+    ]
+
+
 @pytest.fixture
 def subject(state_view: StateView, run_control: RunControlHandler) -> EmptyImpl:
     """An EmptyImpl for testing."""
@@ -36,24 +56,45 @@ def subject(state_view: StateView, run_control: RunControlHandler) -> EmptyImpl:
 
 
 @pytest.mark.parametrize(
-    "current_count,count_param,target_count",
+    "current_stored,count_param,target_stored,removed",
     [
-        pytest.param(0, 0, 0, id="empty-to-empty"),
-        pytest.param(6, 0, 0, id="full-to-empty"),
-        pytest.param(6, 6, 6, id="full-noop"),
-        pytest.param(4, 6, 4, id="size-capped"),
-        pytest.param(4, 3, 3, id="not-full-empty"),
-        pytest.param(6, 7, 6, id="overfull"),
-        pytest.param(3, None, 0, id="default-count"),
+        pytest.param([], 0, [], [], id="empty-to-empty"),
+        pytest.param(
+            _contained_labware(3), 0, [], _contained_labware(3), id="full-to-empty"
+        ),
+        pytest.param(
+            _contained_labware(3), 3, _contained_labware(3), [], id="full-noop"
+        ),
+        pytest.param(
+            _contained_labware(2),
+            3,
+            _contained_labware(2),
+            [],
+            id="cant-increase",
+        ),
+        pytest.param(
+            _contained_labware(3),
+            2,
+            _contained_labware(2),
+            [_contained_labware(3)[-1]],
+            id="not-full-empty",
+        ),
+        pytest.param(
+            _contained_labware(3), 4, _contained_labware(3), [], id="overfull"
+        ),
+        pytest.param(
+            _contained_labware(3), None, [], _contained_labware(3), id="default-count"
+        ),
     ],
 )
 async def test_empty_happypath(
     decoy: Decoy,
     state_view: StateView,
     subject: EmptyImpl,
-    current_count: int,
+    current_stored: list[StackerStoredLabwareGroup],
     count_param: int | None,
-    target_count: int,
+    target_stored: list[StackerStoredLabwareGroup],
+    removed: list[StackerStoredLabwareGroup],
     flex_50uL_tiprack: LabwareDefinition,
 ) -> None:
     """It should empty a valid stacker's labware pool."""
@@ -63,13 +104,16 @@ async def test_empty_happypath(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=current_count,
-        max_pool_count=5,
+        contained_labware_bottom_first=current_stored,
+        max_pool_count=3,
         pool_overlap=0,
     )
     decoy.when(state_view.modules.get_flex_stacker_substate(module_id)).then_return(
         stacker_state
     )
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
     params = EmptyParams(
         moduleId=module_id,
         count=count_param,
@@ -79,12 +123,33 @@ async def test_empty_happypath(
     result = await subject.execute(params)
     assert result.state_update == StateUpdate(
         flex_stacker_state_update=FlexStackerStateUpdate(
-            module_id=module_id, pool_count=target_count
-        )
+            module_id=module_id, contained_labware_bottom_first=target_stored
+        ),
+        batch_labware_location=BatchLabwareLocationUpdate(
+            new_locations_by_id={
+                g.primaryLabwareId: OFF_DECK_LOCATION for g in removed
+            },
+            new_offset_ids_by_id={g.primaryLabwareId: None for g in removed},
+        ),
     )
     assert result.public == EmptyResult(
-        count=target_count,
+        count=len(target_stored),
         primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+        adapterLabwareURI=None,
+        lidLabwareURI=None,
+        storedLabware=target_stored,
+        removedLabware=removed,
+        originalPrimaryLabwareLocationSequences=[
+            [InStackerHopperLocation(moduleId="some-module-id")] for _ in removed
+        ],
+        originalAdapterLabwareLocationSequences=None,
+        originalLidLabwareLocationSequences=None,
+        newPrimaryLabwareLocationSequences=[
+            [NotOnDeckLocationSequenceComponent(logicalLocationName=OFF_DECK_LOCATION)]
+            for _ in removed
+        ],
+        newAdapterLabwareLocationSequences=None,
+        newLidLabwareLocationSequences=None,
     )
 
 
@@ -116,7 +181,7 @@ async def test_empty_requires_constrained_pool(
         pool_primary_definition=None,
         pool_lid_definition=None,
         pool_adapter_definition=None,
-        pool_count=3,
+        contained_labware_bottom_first=_contained_labware(3),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -149,7 +214,7 @@ async def test_pause_strategy_pauses(
 ) -> None:
     """It should pause the system when the pause strategy is used."""
     module_id = "some-module-id"
-    current_count = 3
+    current_count = 2
     count_param = 1
     target_count = 1
     stacker_state = FlexStackerSubState(
@@ -157,27 +222,50 @@ async def test_pause_strategy_pauses(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=current_count,
+        contained_labware_bottom_first=_contained_labware(current_count),
         max_pool_count=5,
         pool_overlap=0,
     )
     decoy.when(state_view.modules.get_flex_stacker_substate(module_id)).then_return(
         stacker_state
     )
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
     params = EmptyParams(
         moduleId=module_id,
         count=count_param,
         message="some-message",
         strategy=StackerFillEmptyStrategy.MANUAL_WITH_PAUSE,
     )
+    primary_id = _contained_labware(2)[1].primaryLabwareId
     result = await subject.execute(params)
     assert result.state_update == StateUpdate(
         flex_stacker_state_update=FlexStackerStateUpdate(
-            module_id=module_id, pool_count=target_count
-        )
+            module_id=module_id,
+            contained_labware_bottom_first=_contained_labware(count_param),
+        ),
+        batch_labware_location=BatchLabwareLocationUpdate(
+            new_locations_by_id={primary_id: OFF_DECK_LOCATION},
+            new_offset_ids_by_id={primary_id: None},
+        ),
     )
     assert result.public == EmptyResult(
         count=target_count,
         primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+        adapterLabwareURI=None,
+        lidLabwareURI=None,
+        storedLabware=_contained_labware(1),
+        removedLabware=_contained_labware(2)[1:],
+        originalPrimaryLabwareLocationSequences=[
+            [InStackerHopperLocation(moduleId="some-module-id")]
+        ],
+        originalAdapterLabwareLocationSequences=None,
+        originalLidLabwareLocationSequences=None,
+        newPrimaryLabwareLocationSequences=[
+            [NotOnDeckLocationSequenceComponent(logicalLocationName=OFF_DECK_LOCATION)]
+        ],
+        newAdapterLabwareLocationSequences=None,
+        newLidLabwareLocationSequences=None,
     )
     decoy.verify(await run_control.wait_for_resume())
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_fill.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_fill.py
index 38d6f3b7526..cb5e0ff95f7 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_fill.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_fill.py
@@ -7,20 +7,35 @@
 from opentrons.protocol_engine.state.update_types import (
     StateUpdate,
     FlexStackerStateUpdate,
+    BatchLabwareLocationUpdate,
+    BatchLoadedLabwareUpdate,
+    LabwareLidUpdate,
 )
 
+
 from opentrons.protocol_engine.state.state import StateView
 from opentrons.protocol_engine.state.module_substates import (
     FlexStackerSubState,
     FlexStackerId,
 )
-from opentrons.protocol_engine.execution import RunControlHandler
+from opentrons.protocol_engine.execution import RunControlHandler, EquipmentHandler
+from opentrons.protocol_engine.execution.equipment import LoadedLabwarePoolData
+from opentrons.protocol_engine.resources import ModelUtils
 from opentrons.protocol_engine.commands.flex_stacker.fill import (
     FillImpl,
     FillParams,
     FillResult,
 )
-from opentrons.protocol_engine.types import StackerFillEmptyStrategy, DeckSlotLocation
+from opentrons.protocol_engine.types import (
+    StackerFillEmptyStrategy,
+    DeckSlotLocation,
+    StackerStoredLabwareGroup,
+    LoadedLabware,
+    OFF_DECK_LOCATION,
+    SYSTEM_LOCATION,
+    InStackerHopperLocation,
+    NotOnDeckLocationSequenceComponent,
+)
 from opentrons.protocol_engine.errors import (
     FlexStackerLabwarePoolNotYetDefinedError,
     ModuleNotLoadedError,
@@ -30,29 +45,59 @@
 
 
 @pytest.fixture
-def subject(state_view: StateView, run_control: RunControlHandler) -> FillImpl:
+def subject(
+    state_view: StateView,
+    run_control: RunControlHandler,
+    model_utils: ModelUtils,
+    equipment: EquipmentHandler,
+) -> FillImpl:
     """A FillImpl for testing."""
-    return FillImpl(state_view=state_view, run_control=run_control)
+    return FillImpl(
+        state_view=state_view,
+        run_control=run_control,
+        equipment=equipment,
+        model_utils=model_utils,
+    )
+
+
+def _contained_labware(count: int) -> list[StackerStoredLabwareGroup]:
+    return [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{i+1}",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        )
+        for i in range(count)
+    ]
 
 
 @pytest.mark.parametrize(
-    "current_count,count_param,max_pool_count",
+    "current_stored,count_param,max_pool_count,expected_new_labware",
     [
-        pytest.param(0, 6, 6, id="empty-to-full"),
-        pytest.param(6, 6, 6, id="full-noop"),
-        pytest.param(6, 4, 6, id="size-minimum"),
-        pytest.param(3, 4, 4, id="fill-not-to-full"),
-        pytest.param(4, 7, 6, id="capped-by-max"),
-        pytest.param(3, None, 6, id="default-count"),
+        pytest.param([], 3, 3, 3, id="empty-to-full"),
+        pytest.param(_contained_labware(3), 3, 3, 0, id="full-noop"),
+        pytest.param(_contained_labware(3), 1, 3, 0, id="size-minimum"),
+        pytest.param(_contained_labware(1), 2, 3, 1, id="fill-not-to-full"),
+        pytest.param(_contained_labware(1), 4, 3, 2, id="capped-by-max"),
+        pytest.param(
+            _contained_labware(1),
+            None,
+            3,
+            2,
+            id="default-count",
+        ),
     ],
 )
-async def test_fill_happypath(
+async def test_fill_by_count_happypath(
     decoy: Decoy,
     state_view: StateView,
+    model_utils: ModelUtils,
+    equipment: EquipmentHandler,
     subject: FillImpl,
-    current_count: int,
+    current_stored: list[StackerStoredLabwareGroup],
     count_param: int | None,
     max_pool_count: int,
+    expected_new_labware: int,
     flex_50uL_tiprack: LabwareDefinition,
 ) -> None:
     """It should fill a valid stacker's labware pool."""
@@ -62,7 +107,7 @@ async def test_fill_happypath(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=current_count,
+        contained_labware_bottom_first=current_stored,
         max_pool_count=max_pool_count,
         pool_overlap=0,
     )
@@ -75,15 +120,90 @@ async def test_fill_happypath(
         message="some-message",
         strategy=StackerFillEmptyStrategy.LOGICAL,
     )
+    primary_ids = iter([f"new-primary-{i+1}" for i in range(expected_new_labware)])
+    decoy.when(model_utils.generate_id()).then_do(lambda: next(primary_ids))
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    for i in range(expected_new_labware):
+        decoy.when(state_view.labware.known(f"new-primary-{i+1}")).then_return(False)
+        decoy.when(
+            await equipment.load_labware_pool_from_definitions(
+                pool_primary_definition=flex_50uL_tiprack,
+                pool_adapter_definition=None,
+                pool_lid_definition=None,
+                location=InStackerHopperLocation(moduleId="some-module-id"),
+                primary_id=f"new-primary-{i+1}",
+                adapter_id=None,
+                lid_id=None,
+            )
+        ).then_return(
+            LoadedLabwarePoolData(
+                primary_labware=LoadedLabware(
+                    id=f"new-primary-{i+1}",
+                    loadName="loadname",
+                    definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+                    location=InStackerHopperLocation(moduleId="some-module-id"),
+                    lid_id=None,
+                    offsetId=None,
+                    displayName=None,
+                ),
+                adapter_labware=None,
+                lid_labware=None,
+            )
+        )
     result = await subject.execute(params)
+    added_labware = [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"new-primary-{i+1}",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        )
+        for i in range(expected_new_labware)
+    ]
+    new_stored_labware = current_stored + added_labware
     assert result.state_update == StateUpdate(
         flex_stacker_state_update=FlexStackerStateUpdate(
-            module_id=module_id, pool_count=max_pool_count
-        )
+            module_id=module_id, contained_labware_bottom_first=new_stored_labware
+        ),
+        batch_loaded_labware=BatchLoadedLabwareUpdate(
+            new_locations_by_id={
+                f"new-primary-{i+1}": InStackerHopperLocation(moduleId="some-module-id")
+                for i in range(expected_new_labware)
+            },
+            offset_ids_by_id={
+                f"new-primary-{i+1}": None for i in range(expected_new_labware)
+            },
+            display_names_by_id={
+                f"new-primary-{i+1}": None for i in range(expected_new_labware)
+            },
+            definitions_by_id={
+                f"new-primary-{i+1}": flex_50uL_tiprack
+                for i in range(expected_new_labware)
+            },
+        ),
+        labware_lid=LabwareLidUpdate(parent_labware_ids=[], lid_ids=[]),
     )
+    assert result.public.storedLabware == new_stored_labware
+    assert result.public.addedLabware == added_labware
     assert result.public == FillResult(
-        count=max_pool_count,
+        count=(len(current_stored) + expected_new_labware),
+        storedLabware=new_stored_labware,
+        addedLabware=added_labware,
         primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+        adapterLabwareURI=None,
+        lidLabwareURI=None,
+        originalPrimaryLabwareLocationSequences=[
+            [NotOnDeckLocationSequenceComponent(logicalLocationName=SYSTEM_LOCATION)]
+            for _ in added_labware
+        ],
+        originalAdapterLabwareLocationSequences=None,
+        originalLidLabwareLocationSequences=None,
+        newPrimaryLabwareLocationSequences=[
+            [InStackerHopperLocation(moduleId="some-module-id")] for _ in added_labware
+        ],
+        newAdapterLabwareLocationSequences=None,
+        newLidLabwareLocationSequences=None,
     )
 
 
@@ -115,7 +235,7 @@ async def test_fill_requires_constrained_pool(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=3,
+        contained_labware_bottom_first=_contained_labware(3),
         max_pool_count=0,
         pool_overlap=0,
     )
@@ -143,41 +263,71 @@ async def test_pause_strategy_pauses(
     decoy: Decoy,
     state_view: StateView,
     run_control: RunControlHandler,
+    model_utils: ModelUtils,
     subject: FillImpl,
     flex_50uL_tiprack: LabwareDefinition,
 ) -> None:
     """It should pause the system when the pause strategy is used."""
-    current_count = 3
-    count_param = 6
+    current_count = 1
     max_pool_count = 6
     module_id = "some-module-id"
+    beginning_contained_labware = _contained_labware(current_count)
     stacker_state = FlexStackerSubState(
         module_id=cast(FlexStackerId, module_id),
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=current_count,
+        contained_labware_bottom_first=beginning_contained_labware,
         max_pool_count=max_pool_count,
         pool_overlap=0,
     )
     decoy.when(state_view.modules.get_flex_stacker_substate(module_id)).then_return(
         stacker_state
     )
+    new_labware = [
+        StackerStoredLabwareGroup(
+            primaryLabwareId="new-primary", adapterLabwareId=None, lidLabwareId=None
+        )
+    ]
     params = FillParams(
         moduleId=module_id,
-        count=count_param,
+        labwareToStore=new_labware,
         message="some-message",
         strategy=StackerFillEmptyStrategy.MANUAL_WITH_PAUSE,
     )
-
+    decoy.when(state_view.labware.known("new-primary")).then_return(True)
+    decoy.when(state_view.geometry.get_location_sequence("new-primary")).then_return(
+        [NotOnDeckLocationSequenceComponent(logicalLocationName=OFF_DECK_LOCATION)]
+    )
+    decoy.when(state_view.labware.get_location("new-primary")).then_return(
+        OFF_DECK_LOCATION
+    )
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    new_contained_labware = beginning_contained_labware + new_labware
     result = await subject.execute(params)
     assert result.state_update == StateUpdate(
         flex_stacker_state_update=FlexStackerStateUpdate(
-            module_id=module_id, pool_count=max_pool_count
-        )
+            module_id=module_id, contained_labware_bottom_first=new_contained_labware
+        ),
+        batch_labware_location=BatchLabwareLocationUpdate(
+            new_locations_by_id={
+                "new-primary": InStackerHopperLocation(moduleId="some-module-id")
+            },
+            new_offset_ids_by_id={"new-primary": None},
+        ),
     )
     assert result.public == FillResult(
-        count=max_pool_count,
+        count=2,
         primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+        storedLabware=new_contained_labware,
+        addedLabware=new_labware,
+        originalPrimaryLabwareLocationSequences=[
+            [NotOnDeckLocationSequenceComponent(logicalLocationName=OFF_DECK_LOCATION)]
+        ],
+        newPrimaryLabwareLocationSequences=[
+            [InStackerHopperLocation(moduleId="some-module-id")]
+        ],
     )
     decoy.verify(await run_control.wait_for_resume())
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_open_latch.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_open_latch.py
index 78ea9ac3eca..71168fc5507 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_open_latch.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_open_latch.py
@@ -44,7 +44,7 @@ async def test_open_latch_command(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=0,
         pool_overlap=0,
     )
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_prepare_shuttle.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_prepare_shuttle.py
index 4eb94c7db51..b5b03efd584 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_prepare_shuttle.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_prepare_shuttle.py
@@ -52,7 +52,7 @@ async def test_home_command(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=0,
         pool_overlap=0,
     )
@@ -87,7 +87,7 @@ async def test_home_command_with_stall_detected(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=0,
         pool_overlap=0,
     )
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_retrieve.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_retrieve.py
index 990088a764d..64070b829d9 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_retrieve.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_retrieve.py
@@ -5,6 +5,7 @@
 import pytest
 from decoy import Decoy, matchers
 from typing import Type, Union
+from unittest.mock import sentinel
 
 from opentrons.drivers.flex_stacker.types import StackerAxis
 from opentrons.hardware_control.modules import FlexStacker, PlatformState
@@ -19,9 +20,8 @@
 from opentrons.protocol_engine.state.update_types import (
     StateUpdate,
     FlexStackerStateUpdate,
-    BatchLoadedLabwareUpdate,
+    BatchLabwareLocationUpdate,
     AddressableAreaUsedUpdate,
-    LabwareLidUpdate,
 )
 from opentrons.protocol_engine.state.module_substates import (
     FlexStackerSubState,
@@ -40,14 +40,15 @@
     OnAddressableAreaLocationSequenceComponent,
     OnCutoutFixtureLocationSequenceComponent,
     LabwareLocationSequence,
-    OnLabwareLocation,
     OnLabwareLocationSequenceComponent,
-    LoadedLabware,
+    StackerStoredLabwareGroup,
+    InStackerHopperLocation,
+    LabwareUri,
+    LabwareOffset,
+    OnLabwareOffsetLocationSequenceComponent,
 )
 from opentrons.protocol_engine.errors import CannotPerformModuleAction
 from opentrons.types import DeckSlotName
-from opentrons.protocol_engine.execution import LoadedLabwareData
-from opentrons.protocol_engine.execution.equipment import LoadedLabwarePoolData
 
 from opentrons_shared_data.labware.labware_definition import (
     LabwareDefinition,
@@ -59,6 +60,19 @@
 )
 
 
+def _contained_labware(
+    count: int, with_lid: bool = False, with_adapter: bool = False
+) -> list[StackerStoredLabwareGroup]:
+    return [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{i+1}",
+            adapterLabwareId=f"adapter-id-{i+1}" if with_adapter else None,
+            lidLabwareId=f"lid-id-{i+1}" if with_lid else None,
+        )
+        for i in range(count)
+    ]
+
+
 def _prep_stacker_own_location(
     decoy: Decoy, state_view: StateView, stacker_id: str
 ) -> None:
@@ -118,7 +132,7 @@ async def test_retrieve_raises_when_empty(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -128,7 +142,7 @@ async def test_retrieve_raises_when_empty(
 
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot retrieve labware from Flex Stacker because it contains no labware",
+        match="Cannot retrieve labware from Flex Stacker in .* because it contains no labware",
     ):
         await subject.execute(data)
 
@@ -145,54 +159,43 @@ async def test_retrieve_primary_only(
     """It should be able to retrieve a labware."""
     data = flex_stacker.RetrieveParams(moduleId=stacker_id)
 
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=5,
         pool_overlap=0,
     )
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
 
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=None
-        )
-    )
-
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {loaded_labware.id: loaded_labware},
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
+    decoy.when(
+        state_view.geometry.get_projected_offset_location(
+            ModuleLocation(moduleId=stacker_id)
+        )
+    ).then_return(sentinel.primary_offset_location)
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            sentinel.primary_offset_location,
+        )
+    ).then_return(
+        LabwareOffset.model_construct(id="offset-id-1")  # type: ignore[call-arg]
+    )
 
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(definitions=[flex_50uL_tiprack])
+        state_view.geometry.get_height_of_stacker_labware_pool(stacker_id)
     ).then_return(4)
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
@@ -203,22 +206,22 @@ async def test_retrieve_primary_only(
 
     assert result == SuccessData(
         public=flex_stacker.RetrieveResult(
-            labwareId="labware-id",
+            labwareId="primary-id-1",
             primaryLocationSequence=_stacker_base_loc_seq(stacker_id),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            originalPrimaryLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
-                new_locations_by_id={"labware-id": ModuleLocation(moduleId=stacker_id)},
-                offset_ids_by_id={"labware-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL"
+            batch_labware_location=BatchLabwareLocationUpdate(
+                new_locations_by_id={
+                    "primary-id-1": ModuleLocation(moduleId=stacker_id)
                 },
-                definitions_by_id={"labware-id": flex_50uL_tiprack},
+                new_offset_ids_by_id={"primary-id-1": "offset-id-1"},
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
-                module_id=stacker_id,
-                pool_count=0,
+                module_id=stacker_id, contained_labware_bottom_first=[]
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
@@ -240,81 +243,59 @@ async def test_retrieve_primary_and_lid(
     """It should be able to retrieve a labware with a lid on it."""
     data = flex_stacker.RetrieveParams(moduleId=stacker_id)
 
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_lid = LoadedLabware(
-        id="lid-id",
-        loadName="opentrons_flex_tiprack_lid",
-        definitionUri="opentrons/opentrons_flex_tiprack_lid/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex Tiprack Lid",
-        location=OnLabwareLocation(labwareId="labware-id"),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=tiprack_lid_def,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(2, with_lid=True),
         max_pool_count=5,
         pool_overlap=0,
     )
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
-
     decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=tiprack_lid_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=loaded_lid
-        )
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_lid_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_tiprack_lid/1"))
+    decoy.when(state_view.labware.get_uri_from_definition(tiprack_lid_def)).then_return(
+        LabwareUri("opentrons/opentrons_flex_tiprack_lid/1")
     )
 
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
-        state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="labware-id"),
-            {
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+        state_view.geometry.get_projected_offset_location(
+            ModuleLocation(moduleId=stacker_id),
+        )
+    ).then_return([sentinel.primary_offset_location])
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            [sentinel.primary_offset_location],
         )
     ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="labware-id", lidId="lid-id")]
-        + _stacker_base_loc_seq(stacker_id)
+        LabwareOffset.model_construct(id="offset-id-1")  # type: ignore[call-arg]
     )
-
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[tiprack_lid_def, flex_50uL_tiprack]
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_tiprack_lid/1",
+            [
+                OnLabwareOffsetLocationSequenceComponent(
+                    labwareUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1"
+                ),
+                sentinel.primary_offset_location,
+            ],
         )
+    ).then_return(None)
+    decoy.when(
+        state_view.geometry.get_height_of_stacker_labware_pool(stacker_id)
     ).then_return(8)
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
@@ -324,46 +305,45 @@ async def test_retrieve_primary_and_lid(
 
     assert result == SuccessData(
         public=flex_stacker.RetrieveResult(
-            labwareId="labware-id",
-            lidId="lid-id",
+            labwareId="primary-id-1",
+            lidId="lid-id-1",
             primaryLocationSequence=_stacker_base_loc_seq(stacker_id),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
             lidLocationSequence=(
                 [
                     OnLabwareLocationSequenceComponent(
-                        labwareId="labware-id", lidId="lid-id"
+                        labwareId="primary-id-1", lidId="lid-id-1"
                     )
                 ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
+            originalPrimaryLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
+            originalLidLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="primary-id-1", lidId="lid-id-1"
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
             lidLabwareURI="opentrons/opentrons_flex_tiprack_lid/1",
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": ModuleLocation(moduleId=stacker_id),
-                    "lid-id": OnLabwareLocation(labwareId="labware-id"),
-                },
-                offset_ids_by_id={"labware-id": None, "lid-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "lid-id": "Opentrons Flex Tiprack Lid",
-                },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "lid-id": tiprack_lid_def,
+                    "primary-id-1": ModuleLocation(moduleId=stacker_id),
                 },
+                new_offset_ids_by_id={"primary-id-1": "offset-id-1", "lid-id-1": None},
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_lid=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
             ),
-            labware_lid=LabwareLidUpdate(
-                parent_labware_ids=["labware-id"], lid_ids=["lid-id"]
-            ),
         ),
     )
 
@@ -381,32 +361,12 @@ async def test_retrieve_primary_and_adapter(
     """It should be able to retrieve a labware on an adapter."""
     data = flex_stacker.RetrieveParams(moduleId=stacker_id)
 
-    loaded_adapter = LoadedLabware(
-        id="adapter-id",
-        loadName="opentrons_flex_96_tiprack_adapter",
-        definitionUri="opentrons/opentrons_flex_96_tiprack_adapter/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Tip Rack Adapter",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=OnLabwareLocation(labwareId=loaded_adapter.id),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=tiprack_adapter_def,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(2, with_adapter=True),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -414,58 +374,48 @@ async def test_retrieve_primary_and_adapter(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
     decoy.when(
-        await equipment.load_labware_from_definition(
-            definition=tiprack_adapter_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            labware_id=None,
-            labware_pending_load={},
-        )
-    ).then_return(LoadedLabwareData("adapter-id", tiprack_adapter_def, None))
-
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
     decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=tiprack_adapter_def,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware,
-            adapter_labware=loaded_adapter,
-            lid_labware=None,
-        )
-    )
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
 
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            ModuleLocation(moduleId=stacker_id),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-            },
+            ModuleLocation(moduleId=stacker_id)
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
-        state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="adapter-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-            },
+        state_view.geometry.get_projected_offset_location(
+            ModuleLocation(moduleId=stacker_id)
+        )
+    ).then_return([sentinel.adapter_offset_location])
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_tiprack_adapter/1",
+            [sentinel.adapter_offset_location],
         )
     ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
-        + _stacker_base_loc_seq(stacker_id)
+        LabwareOffset.model_construct(id="offset-id-1")  # type: ignore[call-arg]
     )
-
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[flex_50uL_tiprack, tiprack_adapter_def]
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            [
+                OnLabwareOffsetLocationSequenceComponent(
+                    labwareUri="opentrons/opentrons_flex_96_tiprack_adapter/1"
+                ),
+                sentinel.adapter_offset_location,
+            ],
         )
+    ).then_return(
+        LabwareOffset.model_construct(id="offset-id-2")  # type: ignore[call-arg]
+    )
+    decoy.when(
+        state_view.geometry.get_height_of_stacker_labware_pool(stacker_id)
     ).then_return(12)
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
@@ -475,35 +425,44 @@ async def test_retrieve_primary_and_adapter(
 
     assert result == SuccessData(
         public=flex_stacker.RetrieveResult(
-            labwareId="labware-id",
-            adapterId="adapter-id",
+            labwareId="primary-id-1",
+            adapterId="adapter-id-1",
             primaryLocationSequence=(
-                [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
             adapterLocationSequence=_stacker_base_loc_seq(stacker_id),
             adapterLabwareURI="opentrons/opentrons_flex_96_tiprack_adapter/1",
+            originalPrimaryLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
+            originalAdapterLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": OnLabwareLocation(labwareId="adapter-id"),
-                    "adapter-id": ModuleLocation(moduleId=stacker_id),
-                },
-                offset_ids_by_id={"labware-id": None, "adapter-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "adapter-id": "Opentrons Flex 96 Tip Rack Adapter",
+                    "adapter-id-1": ModuleLocation(moduleId=stacker_id),
                 },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "adapter-id": tiprack_adapter_def,
+                new_offset_ids_by_id={
+                    "primary-id-1": "offset-id-2",
+                    "adapter-id-1": "offset-id-1",
                 },
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_adapter=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
@@ -526,111 +485,84 @@ async def test_retrieve_primary_adapter_and_lid(
     """It should be able to retrieve a labware on an adapter."""
     data = flex_stacker.RetrieveParams(moduleId=stacker_id)
 
-    loaded_adapter = LoadedLabware(
-        id="adapter-id",
-        loadName="opentrons_flex_96_tiprack_adapter",
-        definitionUri="opentrons/opentrons_flex_96_tiprack_adapter/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Tip Rack Adapter",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=OnLabwareLocation(labwareId=loaded_adapter.id),
-    )
-
-    loaded_lid = LoadedLabware(
-        id="lid-id",
-        loadName="opentrons_flex_tiprack_lid",
-        definitionUri="opentrons/opentrons_flex_tiprack_lid/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex Tiprack Lid",
-        location=OnLabwareLocation(labwareId="labware-id"),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=tiprack_adapter_def,
         pool_lid_definition=tiprack_lid_def,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(
+            2, with_lid=True, with_adapter=True
+        ),
         max_pool_count=5,
         pool_overlap=0,
     )
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
-
     decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=tiprack_adapter_def,
-            pool_lid_definition=tiprack_lid_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware,
-            adapter_labware=loaded_adapter,
-            lid_labware=loaded_lid,
-        )
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_lid_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_tiprack_lid/1"))
+    decoy.when(state_view.labware.get_uri_from_definition(tiprack_lid_def)).then_return(
+        LabwareUri("opentrons/opentrons_flex_tiprack_lid/1")
     )
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
 
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
-        state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="adapter-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+        state_view.geometry.get_projected_offset_location(
+            ModuleLocation(moduleId=stacker_id)
+        )
+    ).then_return([sentinel.adapter_offset_location])
+    decoy.when(
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_tiprack_adapter/1",
+            [sentinel.adapter_offset_location],
         )
     ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
-        + _stacker_base_loc_seq(stacker_id)
+        LabwareOffset.model_construct(id="offset-id-1")  # type: ignore[call-arg]
     )
     decoy.when(
-        state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="labware-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            [
+                OnLabwareOffsetLocationSequenceComponent(
+                    labwareUri="opentrons/opentrons_flex_96_tiprack_adapter/1"
+                ),
+                sentinel.adapter_offset_location,
+            ],
         )
     ).then_return(
-        [
-            OnLabwareLocationSequenceComponent(labwareId="labware-id", lidId="lid-id"),
-            OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None),
-        ]
-        + _stacker_base_loc_seq(stacker_id)
+        LabwareOffset.model_construct(id="offset-id-2")  # type: ignore[call-arg]
     )
-
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[tiprack_lid_def, flex_50uL_tiprack, tiprack_adapter_def]
+        state_view.labware.find_applicable_labware_offset(
+            "opentrons/opentrons_flex_tiprack_lid/1",
+            [
+                OnLabwareOffsetLocationSequenceComponent(
+                    labwareUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1"
+                ),
+                OnLabwareOffsetLocationSequenceComponent(
+                    labwareUri="opentrons/opentrons_flex_96_tiprack_adapter/1"
+                ),
+                sentinel.adapter_offset_location,
+            ],
         )
+    ).then_return(None)
+
+    decoy.when(
+        state_view.geometry.get_height_of_stacker_labware_pool(stacker_id)
     ).then_return(16)
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
@@ -640,11 +572,15 @@ async def test_retrieve_primary_adapter_and_lid(
 
     assert result == SuccessData(
         public=flex_stacker.RetrieveResult(
-            labwareId="labware-id",
-            adapterId="adapter-id",
-            lidId="lid-id",
+            labwareId="primary-id-1",
+            adapterId="adapter-id-1",
+            lidId="lid-id-1",
             primaryLocationSequence=(
-                [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
@@ -653,49 +589,54 @@ async def test_retrieve_primary_adapter_and_lid(
             lidLocationSequence=(
                 [
                     OnLabwareLocationSequenceComponent(
-                        labwareId="labware-id", lidId="lid-id"
+                        labwareId="primary-id-1", lidId="lid-id-1"
                     ),
                     OnLabwareLocationSequenceComponent(
-                        labwareId="adapter-id", lidId=None
+                        labwareId="adapter-id-1", lidId=None
                     ),
                 ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             lidLabwareURI="opentrons/opentrons_flex_tiprack_lid/1",
+            originalPrimaryLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
+            originalLidLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="primary-id-1", lidId="lid-id-1"
+                ),
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
+            originalAdapterLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": OnLabwareLocation(labwareId="adapter-id"),
-                    "adapter-id": ModuleLocation(moduleId=stacker_id),
-                    "lid-id": OnLabwareLocation(labwareId="labware-id"),
-                },
-                offset_ids_by_id={
-                    "labware-id": None,
-                    "adapter-id": None,
-                    "lid-id": None,
-                },
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "adapter-id": "Opentrons Flex 96 Tip Rack Adapter",
-                    "lid-id": "Opentrons Flex Tiprack Lid",
+                    "adapter-id-1": ModuleLocation(moduleId=stacker_id),
                 },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "adapter-id": tiprack_adapter_def,
-                    "lid-id": tiprack_lid_def,
+                new_offset_ids_by_id={
+                    "primary-id-1": "offset-id-2",
+                    "adapter-id-1": "offset-id-1",
+                    "lid-id-1": None,
                 },
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_adapter=True, with_lid=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
             ),
-            labware_lid=LabwareLidUpdate(
-                parent_labware_ids=["labware-id"], lid_ids=["lid-id"]
-            ),
         ),
     )
 
@@ -743,53 +684,22 @@ async def test_retrieve_raises_recoverable_error(
     error_timestamp = datetime(year=2020, month=1, day=2)
 
     data = flex_stacker.RetrieveParams(moduleId=stacker_id)
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
 
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=999,
         pool_overlap=0,
     )
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=None
-        )
-    )
-
-    decoy.when(
-        state_view.geometry.get_predicted_location_sequence(
-            ModuleLocation(moduleId=stacker_id),
-            labware_pending_load={"labware-id": loaded_labware},
-        )
-    ).then_return(_stacker_base_loc_seq(stacker_id))
 
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(definitions=[flex_50uL_tiprack])
+        state_view.geometry.get_height_of_stacker_labware_pool(stacker_id)
     ).then_return(16)
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_set_stored_labware.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_set_stored_labware.py
index 2206d605670..3d95d48d4cf 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_set_stored_labware.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_set_stored_labware.py
@@ -2,21 +2,29 @@
 
 import pytest
 from decoy import Decoy
-from typing import Any, cast
+from typing import Any, cast, ContextManager
 from unittest.mock import sentinel
+from contextlib import nullcontext as does_not_raise
 
 from opentrons.protocol_engine.state.update_types import (
     StateUpdate,
     FlexStackerStateUpdate,
     FlexStackerPoolConstraint,
+    BatchLabwareLocationUpdate,
+    BatchLoadedLabwareUpdate,
+    LabwareLidUpdate,
 )
 
+from opentrons.protocol_engine.resources import ModelUtils
 from opentrons.protocol_engine.state.state import StateView
 from opentrons.protocol_engine.state.module_substates import (
     FlexStackerSubState,
     FlexStackerId,
 )
-from opentrons.protocol_engine.execution import EquipmentHandler
+from opentrons.protocol_engine.execution.equipment import (
+    EquipmentHandler,
+    LoadedLabwarePoolData,
+)
 from opentrons.protocol_engine.commands.command import SuccessData
 from opentrons.protocol_engine.commands.flex_stacker.set_stored_labware import (
     SetStoredLabwareImpl,
@@ -24,7 +32,19 @@
     SetStoredLabwareResult,
     StackerStoredLabwareDetails,
 )
-from opentrons.protocol_engine.types import OverlapOffset
+from opentrons.protocol_engine.types import (
+    OverlapOffset,
+    StackerStoredLabwareGroup,
+    NotOnDeckLocationSequenceComponent,
+    SYSTEM_LOCATION,
+    OFF_DECK_LOCATION,
+    InStackerHopperLocation,
+    OnLabwareLocationSequenceComponent,
+    OnLabwareLocation,
+    LabwareLocation,
+    LabwareLocationSequence,
+    LoadedLabware,
+)
 from opentrons_shared_data.labware.labware_definition import LabwareDefinition
 
 from opentrons.protocol_engine.errors import (
@@ -33,13 +53,17 @@
 
 
 @pytest.fixture
-def subject(state_view: StateView, equipment: EquipmentHandler) -> SetStoredLabwareImpl:
+def subject(
+    state_view: StateView, equipment: EquipmentHandler, model_utils: ModelUtils
+) -> SetStoredLabwareImpl:
     """A FillImpl for testing."""
-    return SetStoredLabwareImpl(state_view=state_view, equipment=equipment)
+    return SetStoredLabwareImpl(
+        state_view=state_view, equipment=equipment, model_utils=model_utils
+    )
 
 
 @pytest.mark.parametrize(
-    "adapter_labware,lid_labware,pool_definition",
+    "adapter_labware,lid_labware,pool_definition,initial_stored_labware,primary_loc_seq_prefixes,lid_loc_seq_prefixes,locations",
     [
         pytest.param(
             StackerStoredLabwareDetails(
@@ -55,6 +79,71 @@ def subject(state_view: StateView, equipment: EquipmentHandler) -> SetStoredLabw
                 lid_definition=sentinel.lid_definition,
                 adapter_definition=sentinel.adapter_definition,
             ),
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId="lid-id-2",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-3",
+                    adapterLabwareId="adapter-id-3",
+                    lidLabwareId="lid-id-3",
+                ),
+            ],
+            [
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-2", lidId=None
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-3", lidId=None
+                    )
+                ],
+            ],
+            [
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-1", lidId="lid-id-1"
+                    ),
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    ),
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-2", lidId="lid-id-2"
+                    ),
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-2", lidId=None
+                    ),
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-3", lidId="lid-id-3"
+                    ),
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-3", lidId=None
+                    ),
+                ],
+            ],
+            {
+                "adapter-id-1": InStackerHopperLocation(moduleId="module-id"),
+                "adapter-id-2": InStackerHopperLocation(moduleId="module-id"),
+                "adapter-id-3": InStackerHopperLocation(moduleId="module-id"),
+            },
             id="all-specified",
         ),
         pytest.param(
@@ -67,6 +156,30 @@ def subject(state_view: StateView, equipment: EquipmentHandler) -> SetStoredLabw
                 lid_definition=None,
                 adapter_definition=None,
             ),
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-3",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            [[], [], []],
+            [[], [], []],
+            {
+                "labware-id-1": InStackerHopperLocation(moduleId="module-id"),
+                "labware-id-2": InStackerHopperLocation(moduleId="module-id"),
+                "labware-id-3": InStackerHopperLocation(moduleId="module-id"),
+            },
             id="none-specified",
         ),
         pytest.param(
@@ -81,6 +194,46 @@ def subject(state_view: StateView, equipment: EquipmentHandler) -> SetStoredLabw
                 lid_definition=sentinel.lid_definition,
                 adapter_definition=None,
             ),
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-1",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-1",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-2",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-2",
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-3",
+                    adapterLabwareId=None,
+                    lidLabwareId="lid-id-3",
+                ),
+            ],
+            [[], [], []],
+            [
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-1", lidId="lid-id-1"
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-2", lidId="lid-id-2"
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="labware-id-3", lidId="lid-id-3"
+                    )
+                ],
+            ],
+            {
+                "labware-id-1": InStackerHopperLocation(moduleId="module-id"),
+                "labware-id-2": InStackerHopperLocation(moduleId="module-id"),
+                "labware-id-3": InStackerHopperLocation(moduleId="module-id"),
+            },
             id="lid-only",
         ),
         pytest.param(
@@ -95,6 +248,46 @@ def subject(state_view: StateView, equipment: EquipmentHandler) -> SetStoredLabw
                 lid_definition=None,
                 adapter_definition=sentinel.adapter_definition,
             ),
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-1",
+                    adapterLabwareId="adapter-id-1",
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-2",
+                    adapterLabwareId="adapter-id-2",
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-id-3",
+                    adapterLabwareId="adapter-id-3",
+                    lidLabwareId=None,
+                ),
+            ],
+            [
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-2", lidId=None
+                    )
+                ],
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-3", lidId=None
+                    )
+                ],
+            ],
+            [[], [], []],
+            {
+                "adapter-id-1": InStackerHopperLocation(moduleId="module-id"),
+                "adapter-id-2": InStackerHopperLocation(moduleId="module-id"),
+                "adapter-id-3": InStackerHopperLocation(moduleId="module-id"),
+            },
             id="adapter-only",
         ),
     ],
@@ -110,6 +303,10 @@ async def test_set_stored_labware_happypath(
     flex_50uL_tiprack: LabwareDefinition,
     tiprack_adapter_def: LabwareDefinition,
     tiprack_lid_def: LabwareDefinition,
+    initial_stored_labware: list[StackerStoredLabwareGroup],
+    primary_loc_seq_prefixes: list[LabwareLocationSequence],
+    lid_loc_seq_prefixes: list[LabwareLocationSequence],
+    locations: dict[str, LabwareLocation],
 ) -> None:
     """It should load all possible main/lid/adapter combos."""
     module_id = "module-id"
@@ -129,7 +326,7 @@ async def test_set_stored_labware_happypath(
         ),
         lidLabware=lid_labware,
         adapterLabware=adapter_labware,
-        initialCount=3,
+        initialStoredLabware=initial_stored_labware,
     )
     decoy.when(state_view.modules.get_flex_stacker_substate(module_id)).then_return(
         FlexStackerSubState(
@@ -137,7 +334,7 @@ async def test_set_stored_labware_happypath(
             pool_primary_definition=None,
             pool_adapter_definition=None,
             pool_lid_definition=None,
-            pool_count=0,
+            contained_labware_bottom_first=[],
             max_pool_count=0,
             pool_overlap=0,
         )
@@ -149,7 +346,7 @@ async def test_set_stored_labware_happypath(
             version=1,
         )
     ).then_return((flex_50uL_tiprack, sentinel.unused))
-
+    offset_ids_by_id: dict[str, str | None] = {}
     if lid_labware:
         decoy.when(
             await equipment.load_definition_for_details(
@@ -183,7 +380,64 @@ async def test_set_stored_labware_happypath(
         )
     ).then_return(sentinel.pool_height)
 
-    # pool_definitions = [x for x in [lid_definition, sentinel.primary_definition, adapter_definition,] if x is not None]
+    for labware_group in initial_stored_labware:
+        decoy.when(
+            state_view.labware.known(labware_group.primaryLabwareId)
+        ).then_return(True)
+        offset_ids_by_id[labware_group.primaryLabwareId] = None
+        if labware_group.adapterLabwareId:
+            decoy.when(
+                state_view.labware.known(labware_group.adapterLabwareId)
+            ).then_return(True)
+            decoy.when(
+                state_view.geometry.get_location_sequence(
+                    labware_group.adapterLabwareId
+                )
+            ).then_return(
+                [
+                    NotOnDeckLocationSequenceComponent(
+                        logicalLocationName=OFF_DECK_LOCATION
+                    )
+                ]
+            )
+            decoy.when(
+                state_view.labware.get_location(labware_group.primaryLabwareId)
+            ).then_return(OnLabwareLocation(labwareId=labware_group.adapterLabwareId))
+            decoy.when(
+                state_view.labware.get_location(labware_group.adapterLabwareId)
+            ).then_return(OFF_DECK_LOCATION)
+            offset_ids_by_id[labware_group.adapterLabwareId] = None
+
+        else:
+            decoy.when(
+                state_view.geometry.get_location_sequence(
+                    labware_group.primaryLabwareId
+                )
+            ).then_return(
+                [
+                    NotOnDeckLocationSequenceComponent(
+                        logicalLocationName=OFF_DECK_LOCATION
+                    )
+                ]
+            )
+            decoy.when(
+                state_view.labware.get_location(labware_group.primaryLabwareId)
+            ).then_return(OFF_DECK_LOCATION)
+
+        if labware_group.lidLabwareId:
+            decoy.when(
+                state_view.labware.known(labware_group.lidLabwareId)
+            ).then_return(True)
+            decoy.when(
+                state_view.labware.get_location(labware_group.lidLabwareId)
+            ).then_return(OnLabwareLocation(labwareId=labware_group.primaryLabwareId))
+            decoy.when(
+                state_view.labware.get_lid_id_by_labware_id(
+                    labware_group.primaryLabwareId
+                )
+            ).then_return(labware_group.lidLabwareId)
+            offset_ids_by_id[labware_group.lidLabwareId] = None
+
     if lid_labware and adapter_labware:
         decoy.when(
             state_view._labware.get_labware_overlap_offsets(
@@ -227,12 +481,73 @@ async def test_set_stored_labware_happypath(
             primaryLabwareDefinition=flex_50uL_tiprack,
             lidLabwareDefinition=lid_definition,
             adapterLabwareDefinition=adapter_definition,
+            storedLabware=initial_stored_labware,
             count=3,
+            originalPrimaryLabwareLocationSequences=[
+                prefix
+                + [
+                    NotOnDeckLocationSequenceComponent(
+                        logicalLocationName=OFF_DECK_LOCATION
+                    )
+                ]
+                for prefix in primary_loc_seq_prefixes
+            ],
+            originalAdapterLabwareLocationSequences=(
+                [
+                    [
+                        NotOnDeckLocationSequenceComponent(
+                            logicalLocationName=OFF_DECK_LOCATION
+                        )
+                    ]
+                    for _ in initial_stored_labware
+                ]
+                if initial_stored_labware[0].adapterLabwareId is not None
+                else None
+            ),
+            originalLidLabwareLocationSequences=(
+                [
+                    prefix
+                    + [
+                        NotOnDeckLocationSequenceComponent(
+                            logicalLocationName=OFF_DECK_LOCATION
+                        )
+                    ]
+                    for prefix in lid_loc_seq_prefixes
+                ]
+                if initial_stored_labware[0].lidLabwareId is not None
+                else None
+            ),
+            newPrimaryLabwareLocationSequences=[
+                prefix + [InStackerHopperLocation(moduleId=module_id)]
+                for prefix in primary_loc_seq_prefixes
+            ],
+            newAdapterLabwareLocationSequences=(
+                [
+                    [InStackerHopperLocation(moduleId=module_id)]
+                    for _ in initial_stored_labware
+                ]
+                if initial_stored_labware[0].adapterLabwareId is not None
+                else None
+            ),
+            newLidLabwareLocationSequences=(
+                [
+                    prefix + [InStackerHopperLocation(moduleId=module_id)]
+                    for prefix in lid_loc_seq_prefixes
+                ]
+                if initial_stored_labware[0].lidLabwareId is not None
+                else None
+            ),
         ),
         state_update=StateUpdate(
             flex_stacker_state_update=FlexStackerStateUpdate(
-                module_id=module_id, pool_constraint=pool_definition, pool_count=3
-            )
+                module_id=module_id,
+                pool_constraint=pool_definition,
+                contained_labware_bottom_first=initial_stored_labware,
+            ),
+            batch_labware_location=BatchLabwareLocationUpdate(
+                new_locations_by_id=locations,
+                new_offset_ids_by_id=offset_ids_by_id,
+            ),
         ),
     )
 
@@ -250,7 +565,11 @@ async def test_set_stored_labware_requires_empty_hopper(
             pool_primary_definition=None,
             pool_adapter_definition=None,
             pool_lid_definition=None,
-            pool_count=2,
+            contained_labware_bottom_first=[
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="hello", adapterLabwareId=None, lidLabwareId=None
+                )
+            ],
             max_pool_count=6,
             pool_overlap=0,
         )
@@ -269,14 +588,67 @@ async def test_set_stored_labware_requires_empty_hopper(
         )
 
 
-@pytest.mark.parametrize("input_count,output_count", [(None, 6), (2, 2), (7, 6)])
+@pytest.mark.parametrize(
+    "input_count,input_labware,output_labware,output_error",
+    [
+        (
+            None,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            does_not_raise(),
+        ),
+        (
+            1,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                )
+            ],
+            does_not_raise(),
+        ),
+        (
+            3,
+            None,
+            [
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-1",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+                StackerStoredLabwareGroup(
+                    primaryLabwareId="labware-2",
+                    adapterLabwareId=None,
+                    lidLabwareId=None,
+                ),
+            ],
+            does_not_raise(),
+        ),
+    ],
+)
 async def test_set_stored_labware_limits_count(
     input_count: int | None,
-    output_count: int,
+    input_labware: list[StackerStoredLabwareGroup] | None,
+    output_labware: list[StackerStoredLabwareGroup],
+    output_error: ContextManager[Any],
     decoy: Decoy,
     state_view: StateView,
     equipment: EquipmentHandler,
     subject: SetStoredLabwareImpl,
+    model_utils: ModelUtils,
     flex_50uL_tiprack: LabwareDefinition,
 ) -> None:
     """It should default and limit the input count."""
@@ -291,14 +663,18 @@ async def test_set_stored_labware_limits_count(
         lidLabware=None,
         adapterLabware=None,
         initialCount=input_count,
+        initialStoredLabware=input_labware,
     )
+    for i in range(len(output_labware)):
+        decoy.when(model_utils.generate_id()).then_return(f"labware-{i+1}")
+
     decoy.when(state_view.modules.get_flex_stacker_substate(module_id)).then_return(
         FlexStackerSubState(
             module_id=cast(FlexStackerId, module_id),
             pool_primary_definition=None,
             pool_adapter_definition=None,
             pool_lid_definition=None,
-            pool_count=0,
+            contained_labware_bottom_first=[],
             max_pool_count=0,
             pool_overlap=0,
         )
@@ -327,27 +703,115 @@ async def test_set_stored_labware_limits_count(
         state_view.modules.stacker_max_pool_count_by_height(
             module_id, sentinel.pool_height, 0.0
         )
-    ).then_return(6)
+    ).then_return(2)
+    # we need to control multiple return values from generate_id and it doesnt take
+    # an argument so we can do this iter side-effecting thing
+    labware_ids = iter(("labware-1", "labware-2"))
+    decoy.when(model_utils.generate_id()).then_do(lambda: next(labware_ids))
+    decoy.when(
+        await equipment.load_labware_pool_from_definitions(
+            pool_primary_definition=flex_50uL_tiprack,
+            pool_adapter_definition=None,
+            pool_lid_definition=None,
+            location=InStackerHopperLocation(moduleId=module_id),
+            primary_id="labware-1",
+            adapter_id=None,
+            lid_id=None,
+        )
+    ).then_return(
+        LoadedLabwarePoolData(
+            primary_labware=LoadedLabware(
+                id="labware-1",
+                loadName="some-loadname",
+                definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+                location=InStackerHopperLocation(moduleId=module_id),
+                lid_id=None,
+                offsetId=None,
+                displayName=None,
+            ),
+            adapter_labware=None,
+            lid_labware=None,
+        )
+    )
+    decoy.when(
+        await equipment.load_labware_pool_from_definitions(
+            pool_primary_definition=flex_50uL_tiprack,
+            pool_adapter_definition=None,
+            pool_lid_definition=None,
+            location=InStackerHopperLocation(moduleId=module_id),
+            primary_id="labware-2",
+            adapter_id=None,
+            lid_id=None,
+        )
+    ).then_return(
+        LoadedLabwarePoolData(
+            primary_labware=LoadedLabware(
+                id="labware-2",
+                loadName="some-loadname",
+                definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+                location=InStackerHopperLocation(moduleId=module_id),
+                lid_id=None,
+                offsetId=None,
+                displayName=None,
+            ),
+            adapter_labware=None,
+            lid_labware=None,
+        )
+    )
 
-    result = await subject.execute(params)
+    with output_error:
+        result = await subject.execute(params)
     assert result == SuccessData(
         public=SetStoredLabwareResult.model_construct(
             primaryLabwareDefinition=flex_50uL_tiprack,
             lidLabwareDefinition=None,
             adapterLabwareDefinition=None,
-            count=output_count,
+            count=len(output_labware),
+            storedLabware=output_labware,
+            originalPrimaryLabwareLocationSequences=[
+                [
+                    NotOnDeckLocationSequenceComponent(
+                        logicalLocationName=SYSTEM_LOCATION
+                    )
+                ]
+                for _ in output_labware
+            ],
+            originalAdapterLabwareLocationSequences=None,
+            originalLidLabwareLocationSequences=None,
+            newPrimaryLabwareLocationSequences=[
+                [InStackerHopperLocation(moduleId="module-id")] for _ in output_labware
+            ],
+            newAdapterLabwareLocationSequences=None,
+            newLidLabwareLocationSequences=None,
         ),
         state_update=StateUpdate(
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=module_id,
                 pool_constraint=FlexStackerPoolConstraint(
-                    max_pool_count=6,
+                    max_pool_count=2,
                     pool_overlap=0,
                     primary_definition=flex_50uL_tiprack,
                     lid_definition=None,
                     adapter_definition=None,
                 ),
-                pool_count=output_count,
-            )
+                contained_labware_bottom_first=output_labware,
+            ),
+            batch_loaded_labware=BatchLoadedLabwareUpdate(
+                new_locations_by_id={
+                    f"labware-{i+1}": InStackerHopperLocation(moduleId="module-id")
+                    for i, _ in enumerate(output_labware)
+                },
+                offset_ids_by_id={
+                    f"labware-{i+1}": None for i, _ in enumerate(output_labware)
+                },
+                display_names_by_id={
+                    f"labware-{i+1}": None for i, _ in enumerate(output_labware)
+                },
+                definitions_by_id={
+                    f"labware-{i+1}": flex_50uL_tiprack
+                    for i, _ in enumerate(output_labware)
+                },
+            ),
+            labware_lid=LabwareLidUpdate(parent_labware_ids=[], lid_ids=[]),
         ),
     )
diff --git a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_store.py b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_store.py
index bf1f2aee614..abc015dd306 100644
--- a/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_store.py
+++ b/api/tests/opentrons/protocol_engine/commands/flex_stacker/test_store.py
@@ -32,9 +32,11 @@
 from opentrons.protocol_engine.commands.flex_stacker.store import StoreImpl
 from opentrons.protocol_engine.types import (
     OnAddressableAreaLocationSequenceComponent,
+    ModuleLocation,
     OnModuleLocationSequenceComponent,
     InStackerHopperLocation,
     OnCutoutFixtureLocationSequenceComponent,
+    StackerStoredLabwareGroup,
 )
 from opentrons.protocol_engine.errors import (
     CannotPerformModuleAction,
@@ -61,6 +63,17 @@ def subject(
     )
 
 
+def _contained_labware(count: int) -> list[StackerStoredLabwareGroup]:
+    return [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{i+1}",
+            adapterLabwareId=None,
+            lidLabwareId=None,
+        )
+        for i in range(count)
+    ]
+
+
 async def test_store_raises_if_full(
     decoy: Decoy,
     equipment: EquipmentHandler,
@@ -77,8 +90,8 @@ async def test_store_raises_if_full(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=6,
-        max_pool_count=6,
+        contained_labware_bottom_first=_contained_labware(3),
+        max_pool_count=3,
         pool_overlap=0,
     )
     decoy.when(
@@ -86,7 +99,7 @@ async def test_store_raises_if_full(
     ).then_return(fs_module_substate)
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot store labware in Flex Stacker while it is full",
+        match="Cannot store labware in Flex Stacker in .* because it is full",
     ):
         await subject.execute(data)
 
@@ -107,7 +120,7 @@ async def test_store_raises_if_carriage_logically_empty(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -119,7 +132,7 @@ async def test_store_raises_if_carriage_logically_empty(
     )
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot store labware if Flex Stacker carriage is empty",
+        match="Flex Stacker in .* cannot store labware because its carriage is empty",
     ):
         await subject.execute(data)
 
@@ -138,7 +151,7 @@ async def test_store_raises_if_not_configured(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=0,
         pool_overlap=0,
     )
@@ -147,7 +160,7 @@ async def test_store_raises_if_not_configured(
     ).then_return(fs_module_substate)
     with pytest.raises(
         FlexStackerLabwarePoolNotYetDefinedError,
-        match="The Flex Stacker has not been configured yet and cannot be filled.",
+        match="The Flex Stacker in .* has not been configured yet and cannot be filled.",
     ):
         await subject.execute(data)
 
@@ -193,7 +206,7 @@ async def test_store_raises_if_stall(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=999,
         pool_overlap=0,
     )
@@ -221,15 +234,21 @@ async def test_store_raises_if_stall(
             OnAddressableAreaLocationSequenceComponent(
                 addressableAreaName="flexStackerV1B4",
             ),
+            OnCutoutFixtureLocationSequenceComponent(
+                cutoutId="cutoutA3", possibleCutoutFixtureIds=["flexStackerModuleV1"]
+            ),
         ]
     )
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            InStackerHopperLocation(moduleId=stacker_id)
+            ModuleLocation(moduleId=stacker_id)
         )
     ).then_return(
         [
-            InStackerHopperLocation(moduleId=stacker_id),
+            OnModuleLocationSequenceComponent(moduleId=stacker_id),
+            OnAddressableAreaLocationSequenceComponent(
+                addressableAreaName="flexStackerV1B4",
+            ),
             OnCutoutFixtureLocationSequenceComponent(
                 cutoutId="cutoutA3", possibleCutoutFixtureIds=["flexStackerModuleV1"]
             ),
@@ -361,7 +380,7 @@ async def test_store_raises_if_labware_does_not_match(
         pool_primary_definition=sentinel.primary,
         pool_adapter_definition=pool_adapter,
         pool_lid_definition=pool_lid,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -407,7 +426,7 @@ async def test_store_raises_if_labware_does_not_match(
 
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot store labware stack that does not correspond with Flex Stacker configuration",
+        match="Cannot store labware stack that does not correspond with the configuration of Flex Stacker",
     ):
         await subject.execute(data)
 
@@ -421,7 +440,7 @@ async def test_store(
     stacker_hardware: FlexStacker,
     flex_50uL_tiprack: LabwareDefinition,
 ) -> None:
-    """It should stroe the labware on the stack."""
+    """It should store the labware on the stack."""
     data = flex_stacker.StoreParams(moduleId=stacker_id)
 
     fs_module_substate = FlexStackerSubState(
@@ -429,7 +448,7 @@ async def test_store(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -457,21 +476,31 @@ async def test_store(
             OnAddressableAreaLocationSequenceComponent(
                 addressableAreaName="flexStackerV1B4",
             ),
+            OnCutoutFixtureLocationSequenceComponent(
+                cutoutId="cutoutA3", possibleCutoutFixtureIds=["flexStackerModuleV1"]
+            ),
         ]
     )
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            InStackerHopperLocation(moduleId=stacker_id)
+            ModuleLocation(moduleId=stacker_id)
         )
     ).then_return(
         [
-            InStackerHopperLocation(moduleId=stacker_id),
+            OnModuleLocationSequenceComponent(moduleId=stacker_id),
+            OnAddressableAreaLocationSequenceComponent(
+                addressableAreaName="flexStackerV1B4",
+            ),
             OnCutoutFixtureLocationSequenceComponent(
                 cutoutId="cutoutA3", possibleCutoutFixtureIds=["flexStackerModuleV1"]
             ),
         ]
     )
 
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+
     result = await subject.execute(data)
 
     decoy.verify(await stacker_hardware.store_labware(labware_height=4), times=1)
@@ -482,15 +511,15 @@ async def test_store(
                 OnAddressableAreaLocationSequenceComponent(
                     addressableAreaName="flexStackerV1B4",
                 ),
-            ],
-            primaryLabwareId="labware-id",
-            eventualDestinationLocationSequence=[
-                InStackerHopperLocation(moduleId=stacker_id),
                 OnCutoutFixtureLocationSequenceComponent(
                     cutoutId="cutoutA3",
                     possibleCutoutFixtureIds=["flexStackerModuleV1"],
                 ),
             ],
+            primaryLabwareId="labware-id",
+            eventualDestinationLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
         ),
         state_update=StateUpdate(
@@ -502,7 +531,16 @@ async def test_store(
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=1,
+                contained_labware_bottom_first=(
+                    [
+                        StackerStoredLabwareGroup(
+                            primaryLabwareId="labware-id",
+                            adapterLabwareId=None,
+                            lidLabwareId=None,
+                        ),
+                    ]
+                    + _contained_labware(1)
+                ),
             ),
         ),
     )
diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_labware.py b/api/tests/opentrons/protocol_engine/commands/test_move_labware.py
index 44e1555988d..1ecf0e48c95 100644
--- a/api/tests/opentrons/protocol_engine/commands/test_move_labware.py
+++ b/api/tests/opentrons/protocol_engine/commands/test_move_labware.py
@@ -805,7 +805,7 @@ async def test_move_labware_raises_for_missing_stacker_shuttle(
             pool_primary_definition=None,
             pool_adapter_definition=None,
             pool_lid_definition=None,
-            pool_count=0,
+            contained_labware_bottom_first=[],
             max_pool_count=0,
             pool_overlap=0,
         )
diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_manual_retrieve.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_manual_retrieve.py
index 87ac9639d82..c56771bd49c 100644
--- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_manual_retrieve.py
+++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_manual_retrieve.py
@@ -3,6 +3,10 @@
 import pytest
 from decoy import Decoy
 
+from opentrons_shared_data.labware.labware_definition import (
+    LabwareDefinition,
+)
+
 from opentrons.hardware_control.modules import FlexStacker
 from opentrons.protocol_engine.resources import ModelUtils
 
@@ -10,9 +14,8 @@
 from opentrons.protocol_engine.state.update_types import (
     StateUpdate,
     FlexStackerStateUpdate,
-    BatchLoadedLabwareUpdate,
+    BatchLabwareLocationUpdate,
     AddressableAreaUsedUpdate,
-    LabwareLidUpdate,
 )
 from opentrons.protocol_engine.state.module_substates import (
     FlexStackerSubState,
@@ -35,19 +38,29 @@
     LabwareLocationSequence,
     OnLabwareLocation,
     OnLabwareLocationSequenceComponent,
-    LoadedLabware,
+    StackerStoredLabwareGroup,
+    InStackerHopperLocation,
+    LabwareUri,
 )
 from opentrons.protocol_engine.errors import CannotPerformModuleAction
 from opentrons.types import DeckSlotName
-from opentrons.protocol_engine.execution import LoadedLabwareData
-from opentrons.protocol_engine.execution.equipment import LoadedLabwarePoolData
 
-from opentrons_shared_data.labware.labware_definition import (
-    LabwareDefinition,
-)
 from opentrons.hardware_control.modules.types import PlatformState
 
 
+def _contained_labware(
+    count: int, with_adapter: bool = False, with_lid: bool = False
+) -> list[StackerStoredLabwareGroup]:
+    return [
+        StackerStoredLabwareGroup(
+            primaryLabwareId=f"primary-id-{i+1}",
+            adapterLabwareId=None if not with_adapter else f"adapter-id-{i+1}",
+            lidLabwareId=None if not with_lid else f"lid-id-{i+1}",
+        )
+        for i in range(count)
+    ]
+
+
 @pytest.fixture
 def stacker_id() -> FlexStackerId:
     """Get a consistent ID for a stacker."""
@@ -123,7 +136,7 @@ async def test_manual_retrieve_raises_when_empty(
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -133,7 +146,7 @@ async def test_manual_retrieve_raises_when_empty(
 
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot retrieve labware from Flex Stacker because it contains no labware",
+        match="Cannot retrieve labware from Flex Stacker in .* because it contains no labware",
     ):
         await subject.execute(data)
 
@@ -150,22 +163,12 @@ async def test_manual_retrieve_primary_only(
     """It should be able to retrieve a labware."""
     data = unsafe.UnsafeManualRetrieveParams(moduleId=stacker_id)
 
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(2),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -173,33 +176,22 @@ async def test_manual_retrieve_primary_only(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
 
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=None
-        )
-    )
-
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {loaded_labware.id: loaded_labware},
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
-
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(definitions=[flex_50uL_tiprack])
-    ).then_return(4)
-
+        state_view.geometry.get_predicted_location_sequence(
+            InStackerHopperLocation(moduleId=stacker_id),
+        )
+    ).then_return([InStackerHopperLocation(moduleId=stacker_id)])
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
     _prep_stacker_own_location(decoy, state_view, stacker_id)
 
     decoy.when(stacker_hardware.platform_state).then_return(PlatformState.EXTENDED)
@@ -208,22 +200,23 @@ async def test_manual_retrieve_primary_only(
 
     assert result == SuccessData(
         public=unsafe.UnsafeManualRetrieveResult(
-            labwareId="labware-id",
+            labwareId="primary-id-1",
             primaryLocationSequence=_stacker_base_loc_seq(stacker_id),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
+            originalPrimaryLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
-                new_locations_by_id={"labware-id": ModuleLocation(moduleId=stacker_id)},
-                offset_ids_by_id={"labware-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL"
+            batch_labware_location=BatchLabwareLocationUpdate(
+                new_locations_by_id={
+                    "primary-id-1": ModuleLocation(moduleId=stacker_id)
                 },
-                definitions_by_id={"labware-id": flex_50uL_tiprack},
+                new_offset_ids_by_id={"primary-id-1": None},
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[_contained_labware(2)[1]],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
@@ -245,32 +238,12 @@ async def test_manual_retrieve_primary_and_lid(
     """It should be able to retrieve a labware with a lid on it."""
     data = unsafe.UnsafeManualRetrieveParams(moduleId=stacker_id)
 
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_lid = LoadedLabware(
-        id="lid-id",
-        loadName="opentrons_flex_tiprack_lid",
-        definitionUri="opentrons/opentrons_flex_tiprack_lid/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex Tiprack Lid",
-        location=OnLabwareLocation(labwareId="labware-id"),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=tiprack_lid_def,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(2, with_lid=True),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -278,97 +251,76 @@ async def test_manual_retrieve_primary_and_lid(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
 
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=tiprack_lid_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=loaded_lid
-        )
-    )
-
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="labware-id"),
-            {
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+            InStackerHopperLocation(moduleId=stacker_id)
         )
-    ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="labware-id", lidId="lid-id")]
-        + _stacker_base_loc_seq(stacker_id)
-    )
-
-    decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[tiprack_lid_def, flex_50uL_tiprack]
-        )
-    ).then_return(8)
+    ).then_return([InStackerHopperLocation(moduleId=stacker_id)])
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
     decoy.when(stacker_hardware.platform_state).then_return(PlatformState.EXTENDED)
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_lid_def)
+    ).then_return("opentrons/opentrons_flex_tiprack_lid/1")
+
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
+    decoy.when(state_view.labware.get_uri_from_definition(tiprack_lid_def)).then_return(
+        LabwareUri("opentrons/opentrons_flex_tiprack_lid/1")
+    )
 
     result = await subject.execute(data)
 
     assert result == SuccessData(
         public=unsafe.UnsafeManualRetrieveResult(
-            labwareId="labware-id",
-            lidId="lid-id",
+            labwareId="primary-id-1",
+            lidId="lid-id-1",
             primaryLocationSequence=_stacker_base_loc_seq(stacker_id),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
             lidLocationSequence=(
                 [
                     OnLabwareLocationSequenceComponent(
-                        labwareId="labware-id", lidId="lid-id"
+                        labwareId="primary-id-1", lidId="lid-id-1"
                     )
                 ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             lidLabwareURI="opentrons/opentrons_flex_tiprack_lid/1",
+            originalPrimaryLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
+            originalLidLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="primary-id-1", lidId="lid-id-1"
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": ModuleLocation(moduleId=stacker_id),
-                    "lid-id": OnLabwareLocation(labwareId="labware-id"),
-                },
-                offset_ids_by_id={"labware-id": None, "lid-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "lid-id": "Opentrons Flex Tiprack Lid",
-                },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "lid-id": tiprack_lid_def,
+                    "primary-id-1": ModuleLocation(moduleId=stacker_id),
                 },
+                new_offset_ids_by_id={"primary-id-1": None, "lid-id-1": None},
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_lid=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
             ),
-            labware_lid=LabwareLidUpdate(
-                parent_labware_ids=["labware-id"], lid_ids=["lid-id"]
-            ),
         ),
     )
 
@@ -386,92 +338,43 @@ async def test_manual_retrieve_primary_and_adapter(
     """It should be able to retrieve a labware on an adapter."""
     data = unsafe.UnsafeManualRetrieveParams(moduleId=stacker_id)
 
-    loaded_adapter = LoadedLabware(
-        id="adapter-id",
-        loadName="opentrons_flex_96_tiprack_adapter",
-        definitionUri="opentrons/opentrons_flex_96_tiprack_adapter/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Tip Rack Adapter",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=OnLabwareLocation(labwareId=loaded_adapter.id),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=tiprack_adapter_def,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(2, with_adapter=True),
         max_pool_count=5,
         pool_overlap=0,
     )
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
-    decoy.when(
-        await equipment.load_labware_from_definition(
-            definition=tiprack_adapter_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            labware_id=None,
-            labware_pending_load={},
-        )
-    ).then_return(LoadedLabwareData("adapter-id", tiprack_adapter_def, None))
-
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=tiprack_adapter_def,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware,
-            adapter_labware=loaded_adapter,
-            lid_labware=None,
-        )
-    )
 
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-            },
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="adapter-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-            },
+            InStackerHopperLocation(moduleId=stacker_id)
         )
-    ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
-        + _stacker_base_loc_seq(stacker_id)
-    )
+    ).then_return([InStackerHopperLocation(moduleId=stacker_id)])
 
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[flex_50uL_tiprack, tiprack_adapter_def]
-        )
-    ).then_return(12)
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_adapter_def)
+    ).then_return("opentrons/opentrons_flex_96_tiprack_adapter/1")
+
+    decoy.when(
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
     decoy.when(stacker_hardware.platform_state).then_return(PlatformState.EXTENDED)
@@ -480,35 +383,41 @@ async def test_manual_retrieve_primary_and_adapter(
 
     assert result == SuccessData(
         public=unsafe.UnsafeManualRetrieveResult(
-            labwareId="labware-id",
-            adapterId="adapter-id",
+            labwareId="primary-id-1",
+            adapterId="adapter-id-1",
             primaryLocationSequence=(
-                [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
             adapterLocationSequence=_stacker_base_loc_seq(stacker_id),
             adapterLabwareURI="opentrons/opentrons_flex_96_tiprack_adapter/1",
+            originalPrimaryLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
+            originalAdapterLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": OnLabwareLocation(labwareId="adapter-id"),
-                    "adapter-id": ModuleLocation(moduleId=stacker_id),
-                },
-                offset_ids_by_id={"labware-id": None, "adapter-id": None},
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "adapter-id": "Opentrons Flex 96 Tip Rack Adapter",
-                },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "adapter-id": tiprack_adapter_def,
+                    "adapter-id-1": ModuleLocation(moduleId=stacker_id),
                 },
+                new_offset_ids_by_id={"primary-id-1": None, "adapter-id-1": None},
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_adapter=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
@@ -531,42 +440,14 @@ async def test_manual_retrieve_primary_adapter_and_lid(
     """It should be able to retrieve a labware on an adapter."""
     data = unsafe.UnsafeManualRetrieveParams(moduleId=stacker_id)
 
-    loaded_adapter = LoadedLabware(
-        id="adapter-id",
-        loadName="opentrons_flex_96_tiprack_adapter",
-        definitionUri="opentrons/opentrons_flex_96_tiprack_adapter/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Tip Rack Adapter",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
-
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=OnLabwareLocation(labwareId=loaded_adapter.id),
-    )
-
-    loaded_lid = LoadedLabware(
-        id="lid-id",
-        loadName="opentrons_flex_tiprack_lid",
-        definitionUri="opentrons/opentrons_flex_tiprack_lid/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex Tiprack Lid",
-        location=OnLabwareLocation(labwareId="labware-id"),
-    )
-
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=tiprack_adapter_def,
         pool_lid_definition=tiprack_lid_def,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(
+            2, with_adapter=True, with_lid=True
+        ),
         max_pool_count=5,
         pool_overlap=0,
     )
@@ -574,69 +455,56 @@ async def test_manual_retrieve_primary_adapter_and_lid(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
 
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=tiprack_adapter_def,
-            pool_lid_definition=tiprack_lid_def,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware,
-            adapter_labware=loaded_adapter,
-            lid_labware=loaded_lid,
-        )
-    )
-
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="adapter-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+            InStackerHopperLocation(moduleId=stacker_id)
+        )
+    ).then_return([InStackerHopperLocation(moduleId=stacker_id)])
+    decoy.when(
+        state_view.geometry.get_predicted_location_sequence(
+            OnLabwareLocation(labwareId="adapter-id-1"),
         )
     ).then_return(
-        [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
+        [OnLabwareLocationSequenceComponent(labwareId="adapter-id-1", lidId=None)]
         + _stacker_base_loc_seq(stacker_id)
     )
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
-            OnLabwareLocation(labwareId="labware-id"),
-            {
-                "adapter-id": loaded_adapter,
-                "labware-id": loaded_labware,
-                "lid-id": loaded_lid,
-            },
+            OnLabwareLocation(labwareId="primary-id-1"),
         )
     ).then_return(
         [
-            OnLabwareLocationSequenceComponent(labwareId="labware-id", lidId="lid-id"),
-            OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None),
+            OnLabwareLocationSequenceComponent(
+                labwareId="primary-id-1", lidId="lid-id-1"
+            ),
+            OnLabwareLocationSequenceComponent(labwareId="adapter-id-1", lidId=None),
         ]
         + _stacker_base_loc_seq(stacker_id)
     )
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(flex_50uL_tiprack)
+    ).then_return("opentrons/opentrons_flex_96_filtertiprack_50ul/1")
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_adapter_def)
+    ).then_return("opentrons/opentrons_flex_96_tiprack_adapter/1")
+    decoy.when(
+        state_view.labware.get_uri_from_definition_unless_none(tiprack_lid_def)
+    ).then_return("opentrons/opentrons_flex_tiprack_lid/1")
 
     decoy.when(
-        state_view.geometry.get_height_of_labware_stack(
-            definitions=[tiprack_lid_def, flex_50uL_tiprack, tiprack_adapter_def]
-        )
-    ).then_return(16)
+        state_view.labware.get_uri_from_definition(flex_50uL_tiprack)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_filtertiprack_50ul/1"))
+    decoy.when(
+        state_view.labware.get_uri_from_definition(tiprack_adapter_def)
+    ).then_return(LabwareUri("opentrons/opentrons_flex_96_tiprack_adapter/1"))
+    decoy.when(state_view.labware.get_uri_from_definition(tiprack_lid_def)).then_return(
+        LabwareUri("opentrons/opentrons_flex_tiprack_lid/1")
+    )
 
     _prep_stacker_own_location(decoy, state_view, stacker_id)
     decoy.when(stacker_hardware.platform_state).then_return(PlatformState.EXTENDED)
@@ -645,11 +513,15 @@ async def test_manual_retrieve_primary_adapter_and_lid(
 
     assert result == SuccessData(
         public=unsafe.UnsafeManualRetrieveResult(
-            labwareId="labware-id",
-            adapterId="adapter-id",
-            lidId="lid-id",
+            labwareId="primary-id-1",
+            adapterId="adapter-id-1",
+            lidId="lid-id-1",
             primaryLocationSequence=(
-                [OnLabwareLocationSequenceComponent(labwareId="adapter-id", lidId=None)]
+                [
+                    OnLabwareLocationSequenceComponent(
+                        labwareId="adapter-id-1", lidId=None
+                    )
+                ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             primaryLabwareURI="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
@@ -658,49 +530,54 @@ async def test_manual_retrieve_primary_adapter_and_lid(
             lidLocationSequence=(
                 [
                     OnLabwareLocationSequenceComponent(
-                        labwareId="labware-id", lidId="lid-id"
+                        labwareId="primary-id-1", lidId="lid-id-1"
                     ),
                     OnLabwareLocationSequenceComponent(
-                        labwareId="adapter-id", lidId=None
+                        labwareId="adapter-id-1", lidId=None
                     ),
                 ]
                 + _stacker_base_loc_seq(stacker_id)
             ),
             lidLabwareURI="opentrons/opentrons_flex_tiprack_lid/1",
+            originalPrimaryLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
+            originalAdapterLocationSequence=[
+                InStackerHopperLocation(moduleId=stacker_id)
+            ],
+            originalLidLocationSequence=[
+                OnLabwareLocationSequenceComponent(
+                    labwareId="primary-id-1", lidId="lid-id-1"
+                ),
+                OnLabwareLocationSequenceComponent(
+                    labwareId="adapter-id-1", lidId=None
+                ),
+                InStackerHopperLocation(moduleId=stacker_id),
+            ],
         ),
         state_update=StateUpdate(
-            batch_loaded_labware=BatchLoadedLabwareUpdate(
+            batch_labware_location=BatchLabwareLocationUpdate(
                 new_locations_by_id={
-                    "labware-id": OnLabwareLocation(labwareId="adapter-id"),
-                    "adapter-id": ModuleLocation(moduleId=stacker_id),
-                    "lid-id": OnLabwareLocation(labwareId="labware-id"),
-                },
-                offset_ids_by_id={
-                    "labware-id": None,
-                    "adapter-id": None,
-                    "lid-id": None,
-                },
-                display_names_by_id={
-                    "labware-id": "Opentrons Flex 96 Filter Tip Rack 50 µL",
-                    "adapter-id": "Opentrons Flex 96 Tip Rack Adapter",
-                    "lid-id": "Opentrons Flex Tiprack Lid",
+                    "adapter-id-1": ModuleLocation(moduleId=stacker_id),
                 },
-                definitions_by_id={
-                    "labware-id": flex_50uL_tiprack,
-                    "adapter-id": tiprack_adapter_def,
-                    "lid-id": tiprack_lid_def,
+                new_offset_ids_by_id={
+                    "primary-id-1": None,
+                    "adapter-id-1": None,
+                    "lid-id-1": None,
                 },
             ),
             flex_stacker_state_update=FlexStackerStateUpdate(
                 module_id=stacker_id,
-                pool_count=0,
+                contained_labware_bottom_first=[
+                    _contained_labware(2, with_adapter=True, with_lid=True)[1]
+                ],
             ),
             addressable_area_used=AddressableAreaUsedUpdate(
                 addressable_area_name="flexStackerV1B4"
             ),
-            labware_lid=LabwareLidUpdate(
-                parent_labware_ids=["labware-id"], lid_ids=["lid-id"]
-            ),
         ),
     )
 
@@ -717,54 +594,34 @@ async def test_manual_retrieve_fails_due_to_platform_state(
 ) -> None:
     """It should raise a CannotPerformModuleAction error."""
     data = unsafe.UnsafeManualRetrieveParams(moduleId=stacker_id)
-    loaded_labware = LoadedLabware(
-        id="labware-id",
-        loadName="opentrons_flex_96_filtertiprack_50ul",
-        definitionUri="opentrons/opentrons_flex_96_filtertiprack_50ul/1",
-        lid_id=None,
-        offsetId=None,
-        displayName="Opentrons Flex 96 Filter Tip Rack 50 µL",
-        location=ModuleLocation(moduleId=stacker_id),
-    )
 
     fs_module_substate = FlexStackerSubState(
         module_id=stacker_id,
         pool_primary_definition=flex_50uL_tiprack,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=1,
+        contained_labware_bottom_first=_contained_labware(1),
         max_pool_count=999,
         pool_overlap=0,
     )
     decoy.when(
         state_view.modules.get_flex_stacker_substate(module_id=stacker_id)
     ).then_return(fs_module_substate)
-    decoy.when(
-        await equipment.load_labware_pool_from_definitions(
-            pool_primary_definition=flex_50uL_tiprack,
-            pool_adapter_definition=None,
-            pool_lid_definition=None,
-            location=ModuleLocation(moduleId=stacker_id),
-            primary_id=None,
-            adapter_id=None,
-            lid_id=None,
-        )
-    ).then_return(
-        LoadedLabwarePoolData(
-            primary_labware=loaded_labware, adapter_labware=None, lid_labware=None
-        )
-    )
 
     decoy.when(
         state_view.geometry.get_predicted_location_sequence(
             ModuleLocation(moduleId=stacker_id),
-            labware_pending_load={"labware-id": loaded_labware},
         )
     ).then_return(_stacker_base_loc_seq(stacker_id))
+    decoy.when(
+        state_view.geometry.get_predicted_location_sequence(
+            InStackerHopperLocation(moduleId=stacker_id)
+        )
+    ).then_return([InStackerHopperLocation(moduleId=stacker_id)])
 
     decoy.when(stacker_hardware.platform_state).then_return(PlatformState.UNKNOWN)
     with pytest.raises(
         CannotPerformModuleAction,
-        match="Cannot manually retrieve a labware from Flex Stacker if the carriage is not in gripper position.",
+        match="Cannot manually retrieve a labware from Flex Stacker in .* if the carriage is not in gripper position.",
     ):
         await subject.execute(data)
diff --git a/api/tests/opentrons/protocol_engine/state/test_flex_stacker_state.py b/api/tests/opentrons/protocol_engine/state/test_flex_stacker_state.py
index c2e5ab3b8f4..120485b601f 100644
--- a/api/tests/opentrons/protocol_engine/state/test_flex_stacker_state.py
+++ b/api/tests/opentrons/protocol_engine/state/test_flex_stacker_state.py
@@ -22,6 +22,7 @@
     AddressableArea,
     PotentialCutoutFixture,
     DeckConfigurationType,
+    StackerStoredLabwareGroup,
 )
 from opentrons_shared_data.robot.types import RobotType
 from opentrons_shared_data.deck.types import DeckDefinitionV5
@@ -121,7 +122,7 @@ def test_add_module_action(
         pool_primary_definition=None,
         pool_adapter_definition=None,
         pool_lid_definition=None,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=0,
         pool_overlap=0,
     )
@@ -169,8 +170,48 @@ def test_get_labware_definition_list(
         pool_primary_definition=primary_def,
         pool_adapter_definition=adapter_def,
         pool_lid_definition=lid_def,
-        pool_count=0,
+        contained_labware_bottom_first=[],
         max_pool_count=5,
         pool_overlap=0,
     )
     assert subject.get_pool_definition_ordered_list() == result
+
+
+def test_get_contained_labware() -> None:
+    """It should present a list of contained labware."""
+    subject = FlexStackerSubState(
+        module_id=FlexStackerId("someModuleId"),
+        pool_primary_definition=sentinel.primary_def,
+        pool_adapter_definition=sentinel.adapter_def,
+        pool_lid_definition=sentinel.lid_def,
+        contained_labware_bottom_first=[
+            StackerStoredLabwareGroup(
+                primaryLabwareId="labware-1",
+                lidLabwareId="lid-1",
+                adapterLabwareId=None,
+            ),
+            StackerStoredLabwareGroup(
+                primaryLabwareId="labware-2",
+                lidLabwareId="lid-2",
+                adapterLabwareId=None,
+            ),
+            StackerStoredLabwareGroup(
+                primaryLabwareId="labware-3",
+                lidLabwareId="lid-3",
+                adapterLabwareId=None,
+            ),
+        ],
+        max_pool_count=5,
+        pool_overlap=0,
+    )
+    assert subject.get_contained_labware() == [
+        StackerStoredLabwareGroup(
+            primaryLabwareId="labware-1", lidLabwareId="lid-1", adapterLabwareId=None
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="labware-2", lidLabwareId="lid-2", adapterLabwareId=None
+        ),
+        StackerStoredLabwareGroup(
+            primaryLabwareId="labware-3", lidLabwareId="lid-3", adapterLabwareId=None
+        ),
+    ]
diff --git a/shared-data/command/schemas/13.json b/shared-data/command/schemas/13.json
index c3b658c6356..6e21d2ac225 100644
--- a/shared-data/command/schemas/13.json
+++ b/shared-data/command/schemas/13.json
@@ -1972,9 +1972,25 @@
             }
           ],
           "default": null,
-          "description": "How full the labware pool should now be. If None, default to the maximum amount of the currently-configured labware the pool can hold. If this number is larger than the maximum the pool can hold, it will be clamped to the maximum. If this number is smaller than the current amount of labware the pool holds, it will be clamped to that minimum. Do not use the value in the parameters as an outside observer; instead, use the count value from the results.",
+          "description": "The number of labware that should be initially stored in the stacker. This number will be silently clamped to\nthe maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker.\n\nThis field works with the initialStoredLabware field in a complex way.\n\nThe following must be true for initialCount to be valid:\n  - It is not specified, and initialStoredLabware is not specified, in which case the stacker will start empty\n  - It is not specified, and initialStoredLabware is specified, in which case the contents of the stacker are entirely\n    determined by initialStoredLabware.\n  - It is specified, and initialStoredLabware is specified, in which case the length of initialStoredLabware must be\n    exactly initialCount, and the contents of the stacker will be determined by initialStoredLabware.\n",
           "title": "Count"
         },
+        "labwareToStore": {
+          "anyOf": [
+            {
+              "items": {
+                "$ref": "#/$defs/StackerStoredLabwareGroup"
+              },
+              "type": "array"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "description": "A list of IDs that should be initially stored in the stacker.\n\nIf specified, the first element of the list is the labware on the physical bottom that will be the first labware retrieved.\n\nThis is a complex field. The following must be true for the field to be valid:\n- If this field is specified, then either initialCount must not be specified, or this field must have exactly initalCount elements\n- Each element must contain an id for each corresponding labware details field (i.e. if lidLabware is specified, each element must have\n  a lidLabwareId) and must not contain an id for a corresponding labware details field that is not specified (i.e., if adapterLabware\n  is not specified, each element must not have an adapterLabwareId).\n\nThe behavior of the command depends on the values of both this field and initialCount.\n- If this field is not specified and initialCount is not specified, the command will create the maximum number of labware objects\n  the stacker can hold according to the labware pool specifications.\n- If this field is not specified and initialCount is specified to be 0, the command will create 0 labware objects and the stacker will be empty.\n- If this field is not specified and initialCount is specified to be non-0, the command will create initialCount labware objects of\n  each specified labware type (primary, lid, and adapter), with appropriate positions, and arbitrary IDs, loaded into the stacker\n- If this field is specified (and therefore initialCount is not specified or is specified to be the length of this field) then the\n  command will create labware objects with the IDs specified in this field and appropriate positions, loaded into the stacker.\n\nBehavior is also different depending on whether the labware identified by ID in this field exist or not. Either all labware specified\nin this field must exist, or all must not exist.\n\nFurther,\n- If the labware exist, they must be of the same type as identified in the primaryLabware field.\n- If the labware exist and the adapterLabware field is specified, each labware must be currently loaded on a labware of the same kind as\n  specified in the adapterLabware field, and that labware must be loaded off-deck\n- If the labware exist and the adapterLabware field is not specified, each labware must be currently loaded off deck directly\n- If the labware exist and the lidLabware field is specified, each labware must currently have a loaded lid of the same kind as specified\n  in the lidLabware field\n- If the labware exist and the lidLabware field is not specified, each labware must not currently have a lid\n- If the labware exist, they must have nothing loaded underneath them or above them other than what is mentioned above\n\nIf all the above are true, when this command executes the labware will be immediately moved into InStackerHopper. If any of the above\nare not true, analysis will fail.\n",
+          "title": "Labwaretostore"
+        },
         "message": {
           "default": null,
           "description": "The message to display on connected clients during a manualWithPause strategy fill.",
@@ -4608,22 +4624,22 @@
       "description": "Input parameters for a labware retrieval command.",
       "properties": {
         "adapterId": {
-          "description": "An optional ID to assign to an adapter. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Adapterid",
           "type": "string"
         },
         "displayName": {
-          "description": "An optional user-specified display name or label for this labware.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Displayname",
           "type": "string"
         },
         "labwareId": {
-          "description": "An optional ID to assign to this labware. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Labwareid",
           "type": "string"
         },
         "lidId": {
-          "description": "An optional ID to assign to a lid. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Lidid",
           "type": "string"
         },
@@ -5084,9 +5100,25 @@
             }
           ],
           "default": null,
-          "description": "The number of labware that should be initially stored in the stacker. This number will be silently clamped to the maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker.",
+          "description": "The number of labware that should be initially stored in the stacker. This number will be silently clamped to\nthe maximum number of labware that will fit; do not rely on the parameter to know how many labware are in the stacker.\n\nThis field works with the initialStoredLabware field in a complex way.\n\nThe following must be true for initialCount to be valid:\n  - It is not specified, and initialStoredLabware is not specified, in which case the stacker will start empty\n  - It is not specified, and initialStoredLabware is specified, in which case the contents of the stacker are entirely\n    determined by initialStoredLabware.\n  - It is specified, and initialStoredLabware is specified, in which case the length of initialStoredLabware must be\n    exactly initialCount, and the contents of the stacker will be determined by initialStoredLabware.\n",
           "title": "Initialcount"
         },
+        "initialStoredLabware": {
+          "anyOf": [
+            {
+              "items": {
+                "$ref": "#/$defs/StackerStoredLabwareGroup"
+              },
+              "type": "array"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "default": null,
+          "description": "A list of IDs that should be initially stored in the stacker.\n\nIf specified, the first element of the list is the labware on the physical bottom that will be the first labware retrieved.\n\nThis is a complex field. The following must be true for the field to be valid:\n- If this field is specified, then either initialCount must not be specified, or this field must have exactly initalCount elements\n- Each element must contain an id for each corresponding labware details field (i.e. if lidLabware is specified, each element must have\n  a lidLabwareId) and must not contain an id for a corresponding labware details field that is not specified (i.e., if adapterLabware\n  is not specified, each element must not have an adapterLabwareId).\n\nThe behavior of the command depends on the values of both this field and initialCount.\n- If this field is not specified and initialCount is not specified, the command will create the maximum number of labware objects\n  the stacker can hold according to the labware pool specifications.\n- If this field is not specified and initialCount is specified to be 0, the command will create 0 labware objects and the stacker will be empty.\n- If this field is not specified and initialCount is specified to be non-0, the command will create initialCount labware objects of\n  each specified labware type (primary, lid, and adapter), with appropriate positions, and arbitrary IDs, loaded into the stacker\n- If this field is specified (and therefore initialCount is not specified or is specified to be the length of this field) then the\n  command will create labware objects with the IDs specified in this field and appropriate positions, loaded into the stacker.\n\nBehavior is also different depending on whether the labware identified by ID in this field exist or not. Either all labware specified\nin this field must exist, or all must not exist.\n\nFurther,\n- If the labware exist, they must be of the same type as identified in the primaryLabware field.\n- If the labware exist and the adapterLabware field is specified, each labware must be currently loaded on a labware of the same kind as\n  specified in the adapterLabware field, and that labware must be loaded off-deck\n- If the labware exist and the adapterLabware field is not specified, each labware must be currently loaded off deck directly\n- If the labware exist and the lidLabware field is specified, each labware must currently have a loaded lid of the same kind as specified\n  in the lidLabware field\n- If the labware exist and the lidLabware field is not specified, each labware must not currently have a lid\n- If the labware exist, they must have nothing loaded underneath them or above them other than what is mentioned above\n\nIf all the above are true, when this command executes the labware will be immediately moved into InStackerHopper. If any of the above\nare not true, analysis will fail.\n",
+          "title": "Initialstoredlabware"
+        },
         "lidLabware": {
           "$ref": "#/$defs/StackerStoredLabwareDetails",
           "default": null,
@@ -5407,6 +5439,40 @@
       "title": "StackerStoredLabwareDetails",
       "type": "object"
     },
+    "StackerStoredLabwareGroup": {
+      "description": "Represents one group of labware stored in a stacker hopper.",
+      "properties": {
+        "adapterLabwareId": {
+          "anyOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "title": "Adapterlabwareid"
+        },
+        "lidLabwareId": {
+          "anyOf": [
+            {
+              "type": "string"
+            },
+            {
+              "type": "null"
+            }
+          ],
+          "title": "Lidlabwareid"
+        },
+        "primaryLabwareId": {
+          "title": "Primarylabwareid",
+          "type": "string"
+        }
+      },
+      "required": ["primaryLabwareId", "adapterLabwareId", "lidLabwareId"],
+      "title": "StackerStoredLabwareGroup",
+      "type": "object"
+    },
     "StatusBarAnimation": {
       "description": "Status Bar animation options.",
       "enum": ["idle", "confirm", "updating", "disco", "off"],
@@ -5832,22 +5898,22 @@
       "description": "Input parameters for a labware retrieval command.",
       "properties": {
         "adapterId": {
-          "description": "An optional ID to assign to an adapter. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Adapterid",
           "type": "string"
         },
         "displayName": {
-          "description": "An optional user-specified display name or label for this labware.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Displayname",
           "type": "string"
         },
         "labwareId": {
-          "description": "An optional ID to assign to this labware. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Labwareid",
           "type": "string"
         },
         "lidId": {
-          "description": "An optional ID to assign to a lid. If None, an ID will be generated.",
+          "description": "Do not use. Present for internal backward compatibility.",
           "title": "Lidid",
           "type": "string"
         },