You are absolutely right to push back. I oversimplified the Execution/Orchestration boundary in my first pass, particularly around the interactive nature of grid exploration and the rigidness of the state machine. Your corrections about `ElementManager` and the need for shot-level prompt iteration are spot on.

Here is the revised, battle-hardened architecture that respects the state machine, handles mixed-mode sequences, and formalizes the grid workflow without polluting `StepRunner`.

---

### 1. Where does Grid Exploration state live? (Orchestrator + Existing States)

You nailed it: **StepRunner is for execution, Orchestrator is for workflow.** Grid exploration is an interactive workflow. 

We do **not** need to add new `grid_*` states to `ExecutionStore`. We can perfectly map the grid workflow to the existing `keyframe` states. Semantically, a grid *is* a keyframe generation.

**The Workflow:**
1. **Orchestrator (`ClientSequenceRunner`)** reads the plan. If a sequence needs a start frame and doesn't have one in `clean/`, it initializes the sequence in ExecutionStore as `keyframe_pending`.
2. **StepRunner** runs `execute_keyframe()` (passing the Gemini grid prompt).
3. **ExecutionStore** transitions: `keyframe_pending` → `keyframe_generating` → `keyframe_complete`.
4. **The Human (via Console Dailies)** sees the grid in the Dailies tab (because it's a completed keyframe take). 
5. **The Human Action:** You look at the grid, pick quadrant 3, crop it, and save it to `frames/ep_001/clean/SEQ01.png`. 
6. **Orchestrator** detects the clean frame exists and bumps the state to `video_pending`, readying it for multi-shot.

*Console Impact:* The Dailies tab already knows how to display `keyframe_complete` images. You just review the grid there, do your manual crop utility, and move on. 

### 2. Handling Mixed-Mode Sequences & Prompt Iteration

"Sequence = Shot" was too rigid. To support multi-shot, single-shot I2V, and individual prompt iteration, **ExecutionStore must track both Sequence-level and Shot-level IDs.**

The `ExecutionStore` doesn't care about hierarchy; it just tracks string IDs.

*   **Multi-Shot Mode:** You run `SEQ01`. StepRunner executes `execute_multi_shot`. The output is one video. ExecutionStore tracks `SEQ01`. Dailies shows `SEQ01` with the concatenated prompt summary.
*   **Single-Shot / Iteration Mode:** Shot 3 of the multi-shot looks bad. You want to run just that shot. The orchestrator runs `execute_video` for `SEQ01_SH03`. ExecutionStore tracks `SEQ01_SH03` as its own entity.
*   **Dailies Review:** The Dailies tab will simply show both `SEQ01` (the full multi-shot take) and `SEQ01_SH03` (the targeted fix take). You can approve whichever one works. 

**How the Orchestrator handles this:**
`ClientSequenceRunner` needs an execution mode flag.
```python
def run_sequence(self, sequence_id, target_shot=None):
    if target_shot:
        # MIXED MODE / PROMPT ITERATION
        # ID: SEQ01_SH03
        # Calls: StepRunner.execute_video()
    else:
        # MULTI-SHOT MODE
        # ID: SEQ01
        # Calls: StepRunner.execute_multi_shot()
```

### 3. The Exact State Flow (Validation Safe)

To satisfy `ExecutionStore`'s strict transition validation without rewriting it, here is the exact lifecycle of a client sequence. 

**Phase 1: Initialization & Grid (If needed)**
*   `init_sequence("SEQ01")` creates the JSON in `state/starsend/shots/SEQ01.json`.
*   Initial state: `keyframe_pending`
*   *StepRunner generates grid...*
*   State: `keyframe_generating`
*   State: `keyframe_complete` (Grid is done. Waiting for JT to crop).

**Phase 2: Video Generation**
*   *Human drops cropped image into `clean/`.*
*   Orchestrator explicitly transitions: `keyframe_complete` → `video_pending`.
*   *Orchestrator calls StepRunner...*
*   StepRunner transitions: `video_pending` → `video_submitted` (API called).
*   StepRunner transitions: `video_submitted` → `video_processing` (Polling Kling).
*   StepRunner transitions: `video_processing` → `video_complete` (Downloaded).

**Phase 3: Approval**
*   *Human clicks "Approve" in Dailies tab.*
*   Console transitions: `video_complete` → `approved`.

*Note on Console Approve flow:* The Console's approve button just updates the JSON file for the given ID. Because `SEQ01.json` is a valid shot file in the existing directory structure, the Approve button will work out of the box.

### 4. The CLI Tool (How to actually run this)

**Option B is the winner:** Build a new `tools/client_generate.py`. 

Do not pollute `test_via_steprunner.py`. That tool is for low-level testing. `client_generate.py` is your actual day-to-day workhorse for Driver Beware. It wraps `ClientSequenceRunner` and handles the element cap logic.

**CLI Design:**
```bash
# Generate a grid for the whole sequence (uses first shot's prompt by default)
python tools/client_generate.py --project driver-beware --seq SEQ01 --mode grid

# Run the standard multi-shot for the sequence (auto-detects clean start frame)
python tools/client_generate.py --project driver-beware --seq SEQ01 --mode multi

# Iteration: Run just shot 3 as a single I2V
python tools/client_generate.py --project driver-beware --seq SEQ01 --shot 3 --mode single
```

### 5. Handling the 3-Element Cap (The Real Element Risk)

Since Kling O3 limits you to 4 elements total, and a start frame counts as 1, you have a hard cap of 3 character/prop elements when doing I2V.

This logic belongs entirely in `ClientSequenceRunner`. The JSON plan dictates priority via array order.

```python
# Inside ClientSequenceRunner.run_sequence()

elements_list = seq_data.get('elements', [])
start_frame_path = self._get_clean_start_frame(sequence_id)

if start_frame_path and len(elements_list) > 3:
    print(f"WARNING: Sequence {sequence_id} has {len(elements_list)} elements + start frame.")
    print(f"Dropping lowest priority element: {elements_list[3:]}")
    elements_list = elements_list[:3] # Keep top 3

fal_elements = self.element_manager.build_fal_elements(elements_list, project_id)
```

### Summary of the Architecture Update

1.  **StepRunner** remains 100% untouched. No new methods.
2.  **State Machine** remains untouched. We map Grid exploration to `keyframe_pending` -> `keyframe_complete`.
3.  **ClientSequenceRunner** handles sequence vs. shot targeting, element truncation (the 3-cap rule), and bridging the client JSON plan to StepRunner.
4.  **ExecutionStore** tracks both `SEQ01` (multi-shot takes) and `SEQ01_SH03` (single-shot fixes).
5.  **Dailies Tab** works as-is, displaying grids as keyframes and videos as videos. (We can build a Sequence Board view later, but Dailies gets you shipping today).
6.  **CLI:** A new `client_generate.py` script drives the orchestrator.