# Tech Spec — Execução e Review em Agent Teams Paralelos no TUI

---

## Visão técnica

Esta Tech Spec específica o despacho de múltiplos agent teams em paralelo nas etapas `Execution` e `Review` do TUI, com painel de progresso em tempo real (`ParallelRunning`), atualização assíncrona via `mpsc` sem bloquear a renderização, barreira de sincronização lógica antes da composição do artefato final e rastreabilidade task→agente→status.

A feature estende o modelo single-receiver atual (`App.pending: Option<Receiver<StageEvent>>`) para um modelo multi-receiver (`Vec<AgentHandle>`), preservando o **reducer pattern** e o **event loop não-bloqueante** existentes. Sem `tokio`, sem `rayon` — mantém-se `std::thread` + `std::sync::mpsc`.

**Decisão técnica central: Opção A — `Vec<AgentHandle>`, um `Receiver<StageEvent>` por agente.**
Motivo: encaixa diretamente no `try_recv` loop atual; cada worker possui seu `Sender` exclusivo (sem locks); `StageEvent` não precisa de tagging; `spawn_stage` é reaproveitado por slot sem reescrita.

---

## Arquitetura

### Diagrama de fluxo

```mermaid
flowchart TD
    subgraph UI["UI thread (event_loop em mod.rs)"]
        EL[Drena N receivers/frame - try_recv por slot] --> RED[App::on_parallel_event]
        RED --> RENDER[ui::render_parallel_running]
        EL --> BARRIER{Todos em estado terminal?}
        BARRIER -- não --> EL
        BARRIER -- sim, 0 erros --> COMPOSE[compose_parallel_artifact]
        BARRIER -- sim, com erros --> DECISION[Screen::ParallelDecision]
        DECISION -- Retentar --> RESPAWN[respawn slots em Erro]
        DECISION -- Aceitar parcial --> COMPOSE
        DECISION -- Cancelar --> BOARD[StageBoard]
        COMPOSE --> SAVE[artifact save → Viewer/Checkpoint]
    end

    subgraph Workers["Worker threads - 1 por AgentSlot"]
        T1[agente task-1] -->|tx1| EL
        T2[agente task-2] -->|tx2| EL
        TN[agente task-N] -->|txN| EL
    end

    PLAN[plan_parallel_slots: lê 04-tasks.md] --> SPAWN[spawn_parallel_agent × N]
    SPAWN --> T1 & T2 & TN
```

### Máquina de estados do TUI (extensão)

```mermaid
stateDiagram-v2
    [*] --> StageBoard
    StageBoard --> ParallelRunning: Confirm em Execution/Review com ≥1 task independente
    StageBoard --> Running: demais etapas - single-agent, inalterado
    ParallelRunning --> ParallelRunning: on_parallel_event por slot
    ParallelRunning --> Composing: barreira fecha, 0 erros
    ParallelRunning --> ParallelDecision: barreira fecha, ≥1 erro
    ParallelDecision --> ParallelRunning: Retentar - respawn dos slots em Erro
    ParallelDecision --> Composing: Aceitar parcial
    ParallelDecision --> StageBoard: Cancelar
    Composing --> Viewer: artefato composto e salvo
    Viewer --> Checkpoint
```

### Protocolo de composição do artefato

1. Cada `AgentHandle` grava fragmento num arquivo temporário com `task_id` + pid no nome (sem conflito).
2. Após barreira, `compose_parallel_artifact` coleta fragmentos na ordem determinística do `04-tasks.md`.
3. Concatena sob seções obrigatórias da etapa (reaproveitando `required_sections_for` de `runner.rs`).
4. Gera tabela de rastreabilidade task→agente→status no topo.
5. Escreve artefato consolidado e chama `build_artifact_save_command` — mesmo caminho de persistência atual.

---

## Dados

### Novos tipos em `src/tui/parallel.rs`

```rust
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AgentSlotStatus {
    Aguardando,
    EmExecucao,
    Concluido,
    Erro,
}

// NÃO Clone — contém Receiver não-Clone
pub struct AgentHandle {
    pub task_id: String,
    pub task_label: String,
    pub rx: std::sync::mpsc::Receiver<StageEvent>,
    pub status: AgentSlotStatus,
    pub trace: Vec<String>,
    pub usage: Option<TokenUsageSample>,
    pub result: Option<StageResult>,
    pub fragment_path: std::path::PathBuf,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TaskRef {
    pub id: String,
    pub title: String,
    pub independent: bool,
}

#[derive(Clone, Debug)]
pub struct SlotResult {
    pub task_id: String,
    pub task_label: String,
    pub status: AgentSlotStatus,
    pub fragment: String,
}
```

### Modificações em `App` (`app.rs`)

```rust
pub struct App {
    // campos existentes mantidos integralmente
    pub pending: Option<Receiver<StageEvent>>,   // single-agent (preservado)
    // NOVO
    pub parallel: Vec<AgentHandle>,
    pub parallel_stage: Option<Stage>,
}

// NOVO em Screen enum
Screen::ParallelRunning
Screen::ParallelDecision
```

`StageEvent` (`runner.rs`) **não muda** — reaproveitado integralmente por slot.

---

## Contratos

### `src/tui/parallel.rs` (novo módulo)

```rust
/// Lê backlog e dependências do 04-tasks.md; devolve tasks com flag de independência.
pub fn plan_parallel_slots(tasks_md: &str) -> Vec<TaskRef>;

/// True se a etapa suporta despacho paralelo no MVP (Execution | Review).
pub fn supports_parallel(stage: Stage) -> bool;

/// Função pura: compõe artefato final a partir dos resultados por slot.
pub fn compose_parallel_artifact(stage: Stage, results: &[SlotResult]) -> String;

/// Tabela Markdown: task → agente → status final.
pub fn traceability_table(results: &[SlotResult]) -> String;
```

### `src/tui/runner.rs` (extensão)

```rust
/// Dispara UM agente de uma task específica numa thread; grava fragmento em fragment_path.
/// Reusa motor codex/deterministic existente sem alteração de StageEvent.
pub fn spawn_parallel_agent(
    exe: PathBuf,
    stage: Stage,
    orchestration_name: String,
    task: TaskRef,
    root: PathBuf,
    fragment_path: PathBuf,
    options: StageRunOptions,
) -> Receiver<StageEvent>;
```

### `src/tui/app.rs` (extensão)

```rust
fn start_parallel_stage(&mut self, stage: Stage, fx: &mut SideEffects);
pub fn on_parallel_event(&mut self, slot_index: usize, event: StageEvent);
pub fn parallel_barrier_reached(&self) -> bool;
fn resolve_parallel_barrier(&mut self, fx: &mut SideEffects);
fn update_parallel_running(&mut self, action: Action);
fn update_parallel_decision(&mut self, action: Action, fx: &mut SideEffects);
```

### `src/tui/mod.rs` (extensão do event loop)

```rust
// Drena todos os slots paralelos, não-bloqueante (extensão do bloco existente)
for idx in 0..app.parallel.len() {
    while let Ok(event) = app.parallel[idx].rx.try_recv() {
        app.on_parallel_event(idx, event);
    }
}
if !app.parallel.is_empty() && app.parallel_barrier_reached() {
    app.resolve_parallel_barrier(&mut fx);
}
```

### `src/tui/ui.rs` (extensão de renderização)

```rust
fn render_parallel_running(frame: &mut Frame, area: Rect, app: &App);
fn render_parallel_decision(frame: &mut Frame, area: Rect, app: &App);
```

---

## Segurança

- Cada worker thread possui seu `Sender` exclusivo (move semântico) — nenhum dado compartilhado mutável; sem `Arc<Mutex<_>>`.
- `fragment_path` inclui `task_id` + pid + nanos para garantir unicidade e evitar colisões entre execuções concorrentes.
- Encerramento limpo: handles de processo são derrubados (Drop) no cancelamento; sem processos órfãos.
- Sem exposição de secrets: as threads recebem apenas os argumentos necessários para seu fragmento, sem acesso ao estado global do `App`.
- `StageEvent::Trace` por slot é limitado ao `TRACE_LIMIT` existente — sem crescimento de memória ilimitado.

---

## Performance

- **Barreira lógica** (não `std::sync::Barrier`): avaliada a cada frame no event loop; sem bloqueio de thread de UI.
- **N threads** (uma por AgentSlot): custo de criação é O(N) × custo de `thread::spawn` — aceitável para N < 20.
- **Drenagem por slot**: O(N × eventos_por_slot/frame) — muito sub-milissegundo para N típico.
- **Fragmentos em disco**: escrita assíncrona por worker; a composição lê N arquivos apenas após a barreira — sem I/O na renderização.
- **Limite padrão recomendado**: `max_parallel_agents = 8` (configurável em `sdd.config.yaml`); acima disso o dispatcher enfileira e despacha em lotes.
- Spinner animado por slot reusa `Loader::current_frame` — zero overhead adicional de renderização.

---

## Observabilidade

- `provider_trace` por slot (`AgentHandle.trace`, limitado a `TRACE_LIMIT`) — acessível na tela `ParallelDecision` por slot.
- `TokenUsageSample` por slot (`AgentHandle.usage`) — somado no total exibido no painel e na sessão.
- Log `stage_finished` emitido por slot ao receber `StageEvent::Finished` (reaproveitando `log_stage_finished` de `mod.rs`) com `task_id` no campo de contexto.
- Telemetria existente (`t` de telemetria no TUI) estendida para incluir evento `parallel_started { stage, slot_count }` e `parallel_finished { stage, done, errors }`.
- Contador global "X de N concluídos" atualizado a cada frame — visível diretamente no painel.

---

## Testes

| Tipo | Alvo | Estratégia |
|------|------|-----------|
| Unitários (puro) | `plan_parallel_slots` | Fixtures de `04-tasks.md` com 0, 1, N independentes; dependência declarada; IDs duplicados |
| Unitários (puro) | `compose_parallel_artifact` | Todos OK; parcial com erro; ordem determinística; seções obrigatórias presentes |
| Unitários (puro) | `traceability_table` | Formatos de status; todos concluídos; todos com erro |
| Unitários (puro) | `parallel_barrier_reached` | Estados mistos; todos Done; todos Erro; vazio |
| Unitários (puro) | `on_parallel_event` | Transições de status corretas; trace isolado por slot |
| Integração | `spawn_parallel_agent` | Fixture com binário `true`/`false`; rx recebe `Finished`; `Disconnected` tratado como Erro |
| Regressão UI | `renders_all_screens_no_panic` | Incluir `ParallelRunning` e `ParallelDecision` em larguras 80 e 120 |
| Regressão | Caminho single-agent | `start_stage` / `pending` inalterados para etapas != Execution/Review |

---

## Riscos

| # | Risco | Severidade | Mitigação |
|---|-------|-----------|-----------|
| R-01 | Formato real de `04-tasks.md` divergir do parser | Alta | Validar contra artefato Tasks real antes de fixar regex; degradar para single-agent quando 0 independentes |
| R-02 | Worker thread panica antes de enviar `Finished` | Média | `try_recv` retorna `Disconnected`; tratar como `Erro` no slot — barreira nunca trava |
| R-03 | Fragmentos com mesmo nome (colisão) | Média | Path inclui `task_id` + pid + nanos — unicidade garantida |
| R-04 | Rate limit explodir com N agentes Codex | Média | `max_parallel_agents` configurável; acumular `usage` por slot; exibir custo total no painel |
| R-05 | Trace ilimitado por slot causando pressão de memória | Baixa | Aplicar `TRACE_LIMIT` por slot (espelha comportamento global atual) |
| R-06 | Barreira nunca fechar (slot preso) | Média | Workers sempre enviam `Finished`; timeout opcional por slot; UI permite Quit durante `ParallelRunning` |
| R-07 | Regressão no caminho single-agent | Alta | `pending` e `start_stage` preservados; paralelismo é caminho aditivo apenas para Execution/Review |
| R-08 | Artefato composto falhar validação de seções | Média | `compose_parallel_artifact` garante todas as seções de `required_sections_for(stage)`; teste assertando presença |

---

## Plano de implementação

| # | Tarefa | Arquivos | Notas |
|---|--------|----------|-------|
| T-01 | Criar `parallel.rs` com tipos e `supports_parallel` | `src/tui/parallel.rs` (novo) | Fundação de dados; testes puros |
| T-02 | Implementar `plan_parallel_slots` | `src/tui/parallel.rs` | Parser de tasks; validar formato real; fallback seguro |
| T-03 | Implementar `compose_parallel_artifact` + `traceability_table` | `src/tui/parallel.rs` | Função pura; testes de seções obrigatórias |
| T-04 | Implementar `spawn_parallel_agent` em `runner.rs` | `src/tui/runner.rs` | Reusa motor existente; grava fragmento por task |
| T-05 | Estender `App`: campos `parallel`, `parallel_stage`, `Screen` novos | `src/tui/app.rs` | `start_parallel_stage`, `on_parallel_event`, `parallel_barrier_reached`, `resolve_parallel_barrier` |
| T-06 | Estender event loop em `mod.rs` | `src/tui/mod.rs` | Drena N receivers; resolve barreira; registrar `pub mod parallel` |
| T-07 | Implementar `render_parallel_running` e `render_parallel_decision` | `src/tui/ui.rs` | Painel de slots, spinner, contador, erros isolados |
| T-08 | Implementar `update_parallel_decision` (RF-09) | `src/tui/app.rs` | Retentar, aceitar parcial, cancelar |
| T-09 | Testes de regressão: single-agent e todas as telas | `src/tui/{app,ui,runner}.rs` | `renders_all_screens_no_panic` + caminho `pending` inalterado |

---

## Rastreabilidade

- Slug: `tui-team-agents-execution-review`
- Origem: `docs/tui-team-agents-execution-review/02-prd.md` (aprovado)
- Caminho: `docs/tui-team-agents-execution-review/03-techspec.md`
- Próximo: `docs/tui-team-agents-execution-review/04-tasks.md`
- Cobertura de requisitos:
  - RF-03 → `render_parallel_running`, `Screen::ParallelRunning`
  - RF-04 → `AgentSlotStatus`
  - RF-06 → `on_parallel_event` + `try_recv` por slot no `mod.rs`
  - RF-07 → `parallel_barrier_reached` + `resolve_parallel_barrier`
  - RF-08 → `AgentHandle.trace`/`status` por slot; render isolado
  - RF-09 → `Screen::ParallelDecision` + `update_parallel_decision`
  - RF-10 → `traceability_table` + composição
  - RF-11 → `plan_parallel_slots` (mapeamento 1:1 task → AgentSlot)
- Arquivos analisados: `src/tui/app.rs`, `src/tui/runner.rs`, `src/tui/mod.rs`, `src/tui/stage.rs`, `src/tui/ui.rs`, `src/tui/loader.rs`
- Arquivos a criar: `src/tui/parallel.rs`
- Arquivos a alterar: `src/tui/app.rs`, `src/tui/runner.rs`, `src/tui/mod.rs`, `src/tui/ui.rs`
