Files
kanban/CHAT_PLAN.md
2026-05-06 19:04:45 +02:00

7.1 KiB

Plan: Chat lateral con tools sobre todo el kanban

Objetivo

Panel de chat a la derecha (Mantine AppShell.Aside) que conversa con un LLM y manipula el kanban via tool-calling: crear/renombrar/mover/borrar columnas y tarjetas, consultar metricas (tiempo en columna, historial), explicar el board, etc.

Arquitectura

+---------------------------+----------------+
|        Board (DnD)        |   Chat Aside   |
|                           |                |
|   Cols / Cards            |  msg msg msg   |
|                           |  [input...]    |
+---------------------------+----------------+
            |                     |
            v                     v
       /api/...            /api/chat (SSE)
            \                     /
             v                   v
              kanban backend (Go)
                |
                +-- llamadas internas a las
                    mismas funciones del backend
                    (no HTTP loopback)
  • No HTTP loopback: el endpoint /api/chat ejecuta los tools llamando directamente a db.* (mismas funciones que usan los handlers HTTP). Cero overhead, cero auth-loop.
  • SSE streaming: respuestas + tool_use deltas streamed al frontend para UX viva.
  • State sharing: el chat ve y modifica el mismo operations.db que la UI. Tras una mutacion via tool, el frontend hace reload() del board para reflejar cambios.

Backend Go

Nuevo paquete apps/kanban/chat/

Archivo Responsabilidad
chat.go Endpoint POST /api/chat con SSE. Loop: send msgs+tools a Claude API, recibe tool_use, ejecuta, reinyecta tool_result, hasta end_turn.
tools.go Catalogo de tools: nombre, JSON schema, dispatch a db.*.
claude.go Cliente Claude API (HTTP directo, sin SDK Go porque no hay oficial). Usa infra.HttpPostJSON + streaming manual.

Tool catalog (mapeo 1:1 con la API REST + extras)

Tool name Input Output DB call
list_board {} {columns,cards} con time_in_column_ms ListColumns + ListCardsWithTime
create_column {name:string} Column CreateColumn
rename_column {id:string, name:string} {} UpdateColumn
delete_column {id:string} {} DeleteColumn
reorder_columns {ids:string[]} {} ReorderColumns
create_card {column_id, requester?, title, description?} Card CreateCard
update_card {id, requester?, title?, description?} {} UpdateCard
delete_card {id} {} DeleteCard
move_card {id, column_id, position?} {} MoveCard (calcula ordered_ids si solo position)
card_history {id} [{column_name,duration_ms,...}] CardHistory
find_cards {query?:string, column_id?:string, requester?:string} Card[] filtro en memoria sobre ListCardsWithTime
column_stats {column_id} {count, total_time_ms, avg_time_ms, oldest_card_id} derivado
bulk_create_cards {column_id, cards:[{requester?,title,description?}]} Card[] loop CreateCard

Tools puros (sin escritura) marcados read_only en metadata para mostrar badge "solo lectura" en UI.

Configuracion

  • Env vars: ANTHROPIC_API_KEY (obligatoria), KANBAN_CHAT_MODEL (default: claude-sonnet-4-6), KANBAN_CHAT_SYSTEM (opcional, override del system prompt).
  • Sistema prompt incluido en el binario:

    Eres asistente del tablero kanban. Antes de modificar, llama list_board para ver el estado. Cuando el usuario nombre tarjetas o columnas, resuelve el id con find_cards/list_board. Confirma cambios destructivos antes de borrar.

  • Coste: prompt-caching del system + tools schema (5 min TTL) → llamadas siguientes baratas.

SSE protocol al frontend

event: text
data: "Voy a mover la tarjeta..."

event: tool_use
data: {"id":"toolu_01","name":"move_card","input":{...}}

event: tool_result
data: {"tool_use_id":"toolu_01","ok":true,"result":{...}}

event: text
data: "Hecho. Ahora esta en Doing."

event: done
data: {"stop_reason":"end_turn","usage":{"input_tokens":1234,"output_tokens":56}}

Frontend

Layout

AppShell con aside={{ width: 360, breakpoint: "md" }}. Toggle con icon en header (IconMessageChatbot) — colapsable para no robar espacio en monitores chicos.

Componentes nuevos

Componente Funcion
ChatPanel.tsx Lista de mensajes + input. Usa EventSource para SSE.
ChatMessage.tsx Renderiza turn: texto markdown, tool_use cards (nombre+input pretty-printed), tool_result chips.
useKanbanChat.ts Hook: estado de turnos, persistencia en localStorage, trigger de onBoardChange (reload) tras cada tool_result exitoso.

Markdown

react-markdown + remark-gfm para tablas/listas en respuestas del LLM.

Botones rapidos en cada tarjeta/columna

ActionIcon "Preguntar al chat sobre esto" inyecta contexto:

Sobre la tarjeta {title} (id {id}): ...

Persistencia chat

Conversaciones por sesion en localStorage (clave kanban_chat_v1). NO se persiste en SQLite por ahora — es state efimero. Si en el futuro se quiere historico → tabla chat_messages en operations.db.

Seguridad

  • Tools de borrado (delete_column, delete_card) requieren confirmacion explicita en el system prompt. Si el LLM las invoca sin confirmar, el frontend abre modal de confirmacion antes de pasar el resultado al loop.
  • ANTHROPIC_API_KEY solo en backend, NUNCA expuesta al frontend.

Hitos (orden de ejecucion sugerido)

  1. Tools.go + tests: catalogo + dispatch puro Go, sin LLM. Probar con curl manual.
  2. Endpoint /api/chat no streaming: request → response sincrono. Validar Claude API + tool loop.
  3. Streaming SSE.
  4. ChatPanel UI + useKanbanChat.
  5. Toggle aside + layout responsive.
  6. Confirmacion de tools destructivos.
  7. Botones contextuales en cards/columns.

Funciones del registry a delegar a fn-constructor (registry-first)

Antes de codear el chat, crear estas primitivas reutilizables:

ID Lenguaje Proposito
claude_messages_call_go_infra Go Wrapper sobre POST https://api.anthropic.com/v1/messages con tools, system, prompt-caching. Impure, error_type.
claude_messages_stream_go_infra Go Idem pero con SSE streaming, retorna canal de eventos. Impure.
sse_writer_go_infra Go Helper para escribir SSE eventos en http.ResponseWriter (set headers, flusher, format event:/data:). Pure factory.

Estas tres son utiles en cualquier app que necesite chat o LLM tools — no se quedan en el kanban.

Decisiones pendientes (preguntar al usuario antes de codear)

  1. Modelo: claude-opus-4-7 (mas capaz, mas caro) vs claude-sonnet-4-6 (default razonable) vs claude-haiku-4-5 (rapido, barato).
  2. Streaming: empezar simple (request/response sincrono) o ir directo a SSE.
  3. Persistencia chat: solo localStorage o tambien tabla en operations.db para historico cross-session.
  4. Confirmaciones destructivas: bloqueo en frontend o solo via prompt? Si bloqueo, ¿qué define "destructivo"?
  5. Limite de turnos por sesion: para evitar tool-loops infinitos, cap (ej. 20 iteraciones por mensaje del usuario).