# Task 011 — Matrix Thread Support

## Objetivo

Permitir que los agentes mantengan conversaciones en threads de Matrix (`m.thread`),
de forma que cada interaccion con un usuario pueda vivir en un hilo separado
en lugar de la timeline principal del room.

Las respuestas del agente deben volver en el hilo y no en la rama principal

## Contexto

Matrix soporta threads via `m.relates_to` con `rel_type: "m.thread"`.
Un thread siempre referencia un **evento raiz** y opcionalmente incluye
`m.in_reply_to` como fallback para clientes sin soporte de threads.

```json
{
  "m.relates_to": {
    "rel_type": "m.thread",
    "event_id": "$rootEventId",
    "is_falling_back": true,
    "m.in_reply_to": {
      "event_id": "$lastEventInThread"
    }
  }
}
```

## Prerequisito

- Task: Reply simple (`m.in_reply_to`) ya implementado.

## Plan de implementacion

### 1. Detectar threads entrantes en el Listener

- En `shell/matrix/listener.go`, al parsear el evento, extraer `m.relates_to`
- Si `rel_type == "m.thread"`, capturar `event_id` como `ThreadRootID`
- Propagar `ThreadRootID` en `MessageContext`

### 2. Extender MessageContext

- `pkg/decision/types.go`: anadir `ThreadRootID string` (el evento raiz del thread)
- Esto es dato puro, no rompe la arquitectura

### 3. Extender ReplyAction

- `pkg/decision/types.go`: anadir `ThreadRootID string` a `ReplyAction`
- El runner usara esto para decidir si enviar como thread o como mensaje normal

### 4. SendThreadMarkdown en Client

- `shell/matrix/client.go`: nuevo metodo `SendThreadMarkdown(ctx, roomID, threadRootID, inReplyTo, markdown)`
- Construye el `m.relates_to` con `rel_type: "m.thread"` + fallback `m.in_reply_to`

### 5. Actualizar effects/Runner

- `shell/effects/runner.go`: si `ReplyAction.ThreadRootID != ""`, usar `SendThreadMarkdown`
- Actualizar interfaz `MatrixSender` con el nuevo metodo

### 6. Propagacion en runtime.go

- Cuando el mensaje entrante ya esta en un thread (`msgCtx.ThreadRootID != ""`),
  las respuestas del bot deben continuar en ese thread
- Cuando el usuario inicia una conversacion nueva, decidir segun config si crear thread o no

### 7. Configuracion por agente

- `internal/config/schema.go`: anadir opcion `matrix.threads.enabled: bool` y
  `matrix.threads.auto_thread: bool` (crear thread automatico por cada conversacion nueva)
- Default: `enabled: true`, `auto_thread: false`

### 8. Memory por thread

- La window de conversacion deberia poder ser por thread en vez de por room
- Si `ThreadRootID != ""`, usar `threadRootID` como key de la window en vez de `roomID`
- Esto permite conversaciones paralelas en threads distintos sin mezclarse

### 9. Tests

- Unit tests para `SendThreadMarkdown` (verificar estructura JSON)
- Test de integracion: listener detecta thread entrante y propaga ThreadRootID
- Test: respuesta dentro de thread mantiene el thread root correcto

## Notas

- `is_falling_back: true` siempre debe estar cuando se usa thread + in_reply_to fallback
- El `event_id` de `m.relates_to` (nivel top) siempre apunta al root del thread, nunca cambia
- El `m.in_reply_to` dentro del thread apunta al ultimo mensaje respondido
- Clientes sin soporte de threads ven el fallback como un reply normal
