diff --git a/.planning/STATE.md b/.planning/STATE.md
index 5f14ec0..a083805 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -28,7 +28,7 @@ See: `.planning/PROJECT.md` (updated 2026-03-28)
Phase: 3 of 4 (Unified Runtime Workspace)
Plan: 3 of 3 authored for current phase
Status: Phase complete — ready for verification
-Last activity: 2026-03-29
+Last activity: 2026-03-30 - Completed quick task 1: Investigar e corrigir bug de internal error na segunda mensagem do chat
Progress: [█████░░░░░] 50%
@@ -91,6 +91,12 @@ None yet.
- None currently; Phase 3 can build on the now-stable lifecycle/apply contract.
+### Quick Tasks Completed
+
+| # | Description | Date | Commit | Directory |
+|---|-------------|------|--------|-----------|
+| 1 | Investigar e corrigir bug de internal error na segunda mensagem do chat | 2026-03-30 | abb9db0 | [1-investigar-e-corrigir-bug-de-internal-er](./quick/1-investigar-e-corrigir-bug-de-internal-er/) |
+
## Session Continuity
Last session: 2026-03-29T20:49:43.436Z
diff --git a/.planning/quick/1-investigar-e-corrigir-bug-de-internal-er/1-PLAN.md b/.planning/quick/1-investigar-e-corrigir-bug-de-internal-er/1-PLAN.md
new file mode 100644
index 0000000..637bec8
--- /dev/null
+++ b/.planning/quick/1-investigar-e-corrigir-bug-de-internal-er/1-PLAN.md
@@ -0,0 +1,282 @@
+---
+phase: quick
+plan: 1
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - crates/inference-core/src/session.rs
+ - crates/inference-server/src/routes.rs
+ - tauri-app/src/components/ChatView.tsx
+autonomous: true
+requirements: []
+must_haves:
+ truths:
+ - "A segunda mensagem do chat recebe resposta válida do modelo, não 'internal error'"
+ - "Nenhuma mensagem duplicada com erro é inserida no DB após uma geração bem-sucedida"
+ - "O backend loga claramente a causa quando llama_decode falha no prefill"
+ artifacts:
+ - path: "crates/inference-core/src/session.rs"
+ provides: "decode_prefill com log de erro detalhado e fallback de kv_clear via llama_kv_cache_clear"
+ - path: "crates/inference-server/src/routes.rs"
+ provides: "StreamItem::Error não emite conteúdo 'internal error' acumulável pelo frontend"
+ - path: "tauri-app/src/components/ChatView.tsx"
+ provides: "buildApiMessages exclui mensagens de erro do histórico enviado ao backend"
+ key_links:
+ - from: "crates/inference-core/src/engine.rs new_session()"
+ to: "llama_get_memory / memory_clear"
+ via: "KV cache clear antes de cada nova sessão"
+ pattern: "memory_clear"
+ - from: "tauri-app/src/components/ChatView.tsx buildApiMessages"
+ to: "SELECT role, content FROM messages"
+ via: "histórico de mensagens enviado ao backend"
+ pattern: "buildApiMessages"
+---
+
+
+Identificar e corrigir o bug que causa "internal error" na segunda mensagem do chat.
+
+Purpose: A segunda request ao backend falha com llama_decode retornando não-zero no prefill,
+provavelmente porque o KV cache não está sendo limpo corretamente quando llama_get_memory
+retorna NULL. O StreamItem::Error no frontend então acumula "internal error" como conteúdo
+e o salva no DB como mensagem de assistente válida, criando a entrada duplicada observada nos logs.
+
+Output: Backend com KV cache clear robusto + log de diagnóstico + frontend filtrando mensagens
+de erro do histórico da API.
+
+
+
+@C:/Users/Evandro/.claude/get-shit-done/workflows/execute-plan.md
+@C:/Users/Evandro/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/STATE.md
+
+
+
+
+
+ Task 1: Tornar o KV cache clear robusto e adicionar diagnóstico de falha de prefill
+
+ crates/inference-core/src/engine.rs
+ crates/inference-core/src/session.rs
+
+
+**Problema identificado:** Em `engine.rs::new_session()` (linha ~302), quando `llama_get_memory`
+retorna NULL, o KV cache NÃO é limpo e um warning é logado — mas a execução continua.
+Na segunda request, o contexto llama ainda tem tokens da sessão anterior nas posições 0..N,
+e o prefill da nova request tenta escrever novamente nessas mesmas posições causando
+`llama_decode` retornar não-zero (erro KV cache cheio / inconsistência de posições).
+
+**Fix em `engine.rs`:** No método `new_session()`, após a tentativa com `get_memory/memory_clear`,
+adicionar fallback usando `llama_kv_cache_clear(ctx)` diretamente se a memória for NULL.
+A função `llama_kv_cache_clear` existe como símbolo separado em versões mais antigas da
+llama.cpp e deve estar disponível via `self.lib`. Verificar se `LlamaLib` expõe
+`kv_cache_clear` — se sim, chamar como fallback. Se não existir, usar
+`(self.lib.memory_clear)(memory, true)` mas garantir que o código falhe explicitamente
+(retorne `Err`) quando memory é NULL em vez de continuar silenciosamente.
+
+Alterar o bloco atual:
+```rust
+// ANTES (silently continues when memory is NULL):
+let memory = (self.lib.get_memory)(self.ctx.ptr);
+if memory.is_null() {
+ tracing::warn!("llama_get_memory returned NULL — KV cache will not be cleared");
+} else {
+ (self.lib.memory_clear)(memory, true);
+ tracing::debug!("engine.kv_cache_cleared");
+}
+```
+
+Para:
+```rust
+// DEPOIS: bail quando não consegue limpar o cache, evitando decode com estado corrompido
+let memory = (self.lib.get_memory)(self.ctx.ptr);
+if memory.is_null() {
+ tracing::error!("llama_get_memory returned NULL — cannot clear KV cache, refusing to create session to avoid decode corruption");
+ anyhow::bail!("KV cache clear failed: llama_get_memory returned NULL");
+} else {
+ (self.lib.memory_clear)(memory, true);
+ tracing::info!("engine.kv_cache_cleared");
+}
+```
+
+**Fix em `session.rs`:** Em `decode_prefill()` (linha ~350), quando `lib.decode` retorna não-zero,
+melhorar o bail com contexto claro:
+```rust
+// ANTES:
+bail!(
+ "llama_decode (prefill) returned {} while decoding prompt chunk {:?}",
+ ret,
+ plan.range
+);
+
+// DEPOIS:
+bail!(
+ "llama_decode (prefill) returned {} for chunk {:?} — likely KV cache not cleared or context window ({} tokens) exceeded by prompt",
+ ret,
+ plan.range,
+ tokens.len()
+);
+```
+
+Após as mudanças, verificar compilação: `cargo check -p inference-core -p inference-server`
+
+
+ cargo check -p inference-core -p inference-server 2>&1 | tail -5
+
+
+ - `cargo check` passa sem erros
+ - `new_session()` retorna `Err` quando `llama_get_memory` retorna NULL em vez de continuar
+ - Mensagem de erro de prefill inclui tamanho do prompt para diagnóstico
+
+
+
+
+ Task 2: Impedir que "internal error" seja salvo no DB e excluir mensagens de erro do histórico da API
+
+ crates/inference-server/src/routes.rs
+ tauri-app/src/components/ChatView.tsx
+
+
+**Problema 1 — routes.rs:** Quando `StreamItem::Error` é enviado (linha ~318-335), o backend
+emite um chunk SSE com `content: Some("internal error")`. O frontend acumula esse token em
+`fullContent` normalmente (mesmo path do streaming), então salva no DB como assistente com
+o texto "internal error". O `StreamItem::Error` deve sinalizar falha sem emitir conteúdo
+que o frontend interpreta como resposta válida.
+
+Fix: Alterar `StreamItem::Error` para emitir um chunk com `finish_reason: Error` e conteúdo
+vazio, ou usar um campo de erro fora da estrutura `delta.content`. A solução mais simples
+compatível com o frontend atual: emitir o chunk de erro com `content: None` e usar
+`finish_reason: Stop` para que o frontend complete o stream, mas não acumule conteúdo.
+
+```rust
+// ANTES: emite "internal error" como content
+StreamItem::Error => {
+ tracing::error!(streamed_tokens, "stream.error: session failed mid-stream");
+ let chunk = ChatCompletionChunk {
+ // ...
+ choices: vec![ChunkChoice {
+ delta: Delta { role: None, content: Some("internal error".to_owned()), tool_calls: None },
+ finish_reason: Some(FinishReason::Stop),
+ }],
+ usage: None,
+ };
+ yield Ok(Event::default().data(serde_json::to_string(&chunk).unwrap()));
+ yield Ok(Event::default().data("[DONE]"));
+ break;
+}
+
+// DEPOIS: emite chunk final sem content para não poluir fullContent
+StreamItem::Error => {
+ tracing::error!(streamed_tokens, "stream.error: session failed — sending empty finish chunk");
+ let chunk = ChatCompletionChunk {
+ id: id.clone(),
+ object: "chat.completion.chunk",
+ created,
+ model: resolved_model.clone(),
+ choices: vec![ChunkChoice {
+ index: 0,
+ delta: Delta { role: None, content: None, tool_calls: None },
+ finish_reason: Some(FinishReason::Stop),
+ }],
+ usage: None,
+ };
+ yield Ok(Event::default().data(serde_json::to_string(&chunk).unwrap()));
+ yield Ok(Event::default().data("[DONE]"));
+ break;
+}
+```
+
+**Problema 2 — ChatView.tsx:** Em `buildApiMessages` (linha 126-132), o SELECT não filtra
+mensagens de erro. Se alguma mensagem de erro entrou no DB (antes do fix acima), ela vai
+no histórico enviado ao backend, corrompendo o contexto de chat. Adicionar filtro:
+
+```typescript
+// ANTES:
+const rows = db.query<{ role: string; content: string }>(
+ "SELECT role, content FROM messages WHERE chat_id = ? ORDER BY created_at DESC LIMIT ?",
+ [cid, limit]
+).reverse();
+
+// DEPOIS: excluir mensagens que são erros do frontend (⚠️ prefix) e vazias
+const rows = db.query<{ role: string; content: string }>(
+ "SELECT role, content FROM messages WHERE chat_id = ? AND content != '' AND content NOT LIKE '⚠️ Error:%' AND content != 'internal error' ORDER BY created_at DESC LIMIT ?",
+ [cid, limit]
+).reverse();
+```
+
+**Problema 3 — ChatView.tsx:** No catch do `send()` (linha ~261-268), quando o stream termina
+com `fullContent` vazio (após o fix do backend), a lógica atual salva a mensagem de erro do
+catch com prefix `⚠️ Error:`. Mas quando o stream completa normalmente mas com `fullContent`
+vazio (caso de session fail silencioso), a mensagem assistente salva é vazia. Adicionar
+guarda antes do INSERT do assistente:
+
+```typescript
+// Antes do INSERT da mensagem assistente, após a geração completar:
+// Só salvar se houver conteúdo real
+if (!finalContent && !parsed.reasoning) {
+ console.warn("[ChatView] send: assistant returned empty content — not saving to DB");
+} else {
+ db.run(`INSERT INTO messages ...`, [...]);
+}
+```
+
+Localizar a linha do INSERT (linha ~241-254 em ChatView.tsx) e envolver com esse guard.
+
+Após as mudanças:
+- `cargo check -p inference-server`
+- `cd tauri-app && npx tsc --noEmit`
+
+
+ cd D:/Documents/Github/ai-backend-studio && cargo check -p inference-server 2>&1 | tail -3 && cd tauri-app && npx tsc --noEmit 2>&1 | tail -5
+
+
+ - `cargo check` e `tsc --noEmit` passam sem erros
+ - Chunk SSE de erro não contém `content: "internal error"`
+ - `buildApiMessages` filtra mensagens de erro/vazias do histórico
+ - INSERT do assistente não executa quando `fullContent` está vazio
+
+
+
+
+
+ Fix de três camadas para o bug de "internal error" na segunda mensagem:
+ 1. Backend Rust: KV cache clear agora falha explicitamente (Err) quando llama_get_memory
+ retorna NULL, com log de erro claro em vez de continuar silenciosamente
+ 2. Backend Rust: StreamItem::Error emite chunk sem content (não polui fullContent)
+ 3. Frontend: buildApiMessages filtra mensagens de erro do histórico; INSERT do assistente
+ tem guard contra conteúdo vazio
+
+
+ 1. Inicie o runtime local com um modelo GGUF carregado
+ 2. Abra o ForgeLM e envie uma primeira mensagem — confirme resposta normal
+ 3. Envie uma segunda mensagem no mesmo chat — confirme que resposta é válida (não "internal error")
+ 4. Verifique o log do backend no terminal:
+ - Deve aparecer "engine.kv_cache_cleared" antes de cada sessão
+ - Se aparecer "llama_get_memory returned NULL", o runtime vai retornar erro 500 (não "internal error" silencioso)
+ 5. Abra DevTools > Console no app e confirme que não há INSERT com conteúdo "internal error" nos logs [DB]
+ 6. Envie 3-4 mensagens em sequência e confirme que todas respondem normalmente
+
+ Digite "aprovado" se as respostas estão corretas, ou descreva o comportamento observado
+
+
+
+
+
+- `cargo check -p inference-core -p inference-server` passa
+- `cd tauri-app && npx tsc --noEmit` passa
+- Segundo envio de mensagem no chat retorna resposta do modelo (não "internal error")
+- Logs do backend mostram "engine.kv_cache_cleared" antes de cada sessão
+
+
+
+- Enviar 5 mensagens consecutivas no mesmo chat sem receber "internal error"
+- DB não contém nenhuma mensagem assistente com conteúdo "internal error" ou vazio após geração bem-sucedida
+- Se o KV cache falhar (llama_get_memory NULL), o erro é visível como HTTP 500, não como mensagem de assistente corrupta
+
+
+