shortcoder eh uma TUI (Terminal User Interface) no estilo classico dos anos 90, construida em AdvPL/TLPP e compilada com AdvPP.
- Interfaceretrô: ASCII art, cores vintage (amarelo, ciano, verde, magenta), caixas delimitadas
- Orquestracao de agentes: Roteamento automatico baseado no dominio da pergunta
- 100% legivel: Texto quebrado corretamente, padding dinamico, caixas proporcionais
- 3 agentes integrados:
ollama: LLM rapido (~1s) para tarefas geraismem0: Memoria persistente (<1s) para consultar/salvar fatosernesto: RAG + Memoria (>30s) para perguntas de dominio Protheus
┌─────────────────────────────────────────────────────────────────┐
│ shortcoder │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ORQUESTRADOR │ │
│ │ DetectAgent(cInput, cDefaultAgent) │ │
│ │ ├─ Keywords mem0 → agente mem0 │ │
│ │ ├─ Keywords Protheus → agente ernesto (RAG) │ │
│ │ └─ Padrão → agente ollama (LLM) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │
│ │ ollama │ │ mem0 │ │ ernesto │ │
│ │ (rapido)│ │(memoria)│ │ (RAG) │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │
│ │ Ollama │ │ ernesto │ │ ernesto │ │
│ │ :11434 │ │ :9081 │ │ :9081 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
- AdvPP compilador (
advplc) instalado em~/.local/bin/ - Ollama rodando em
http://127.0.0.1:11434 - ernesto-mem0 rodando em
http://127.0.0.1:9081 - Modelo
lfm25-1b-uncensored:latestcarregado no Ollama
cd ~/Projetos/shortcoder
advplc build shortcoder.prw -o shortcoder./shortcoder╔══════════════════════════════════════════════════════════════════════════╗
║ ║
║ SHORTCODER ║
║ ║
║ [AI Coding Agent v1.0] ║
║ ║
╚══════════════════════════════════════════════════════════════════════════╝
┌──────────────────────────────────────────────────────────────────────────┐
│ MODEL: [lfm25-1b-uncensored:latest] │
│ SESSION:[user-20260802-...] │
│ AGENT: [OLLAMA] │
│ MEM0: [default] │
└──────────────────────────────────────────────────────────────────────────┘
>
| Comando | Descricao | Agente |
|---|---|---|
/agent |
Alterna entre ollama, mem0, ernesto, agnes e orchestrator | — |
/model |
Lista e seleciona modelo | — |
/mem0 list |
Lista memorias salvas | mem0 |
/mem0 add <texto> |
Adiciona memoria persistente | mem0 |
/mem0 clear |
Remove todas as memorias | mem0 |
/history |
Mostra historico de turnos | — |
/clear |
Nova sessao (limpa historico) | — |
/help |
Esta ajuda | — |
/tools selftest |
Auto-teste do harness de ferramentas do Agnes | — |
/exit |
Sair do programa | — |
O sistema detecta automaticamente o agente mais adequado:
| Entrada do Usuario | Agente Selecionado | Razao |
|---|---|---|
| "o que e A1_COD?" | ernesto (RAG) | Contem palavra-chave "campo" |
| "/mem0 list" | mem0 | Comando mem0 explicito |
| "2+2" | ollama (LLM) | Pergunta geral |
| "escreva um poema" | ollama (LLM) | Tarefa criativa |
| "como funciona SE1?" | ernesto (RAG) | Contem palavra-chave Protheus |
O sistema detecta automaticamente perguntas sobre:
- Campos: campo, tabela, sx2, sx3, six
- Formularios: formulario, rotina
- Consultas: query, sql, trigger, indice
- Modulos: protheus, advpl, tlpp, totvs, mata, finan
- Tabelas: se1, sa1, sb1, sc1, sd1, sf1, sg1, sh1
- Funcoes: d_e_l_e_t_, xfilial, fwexecstatement, tcquery, dbquery
Ao selecionar ollama ou agnes (direto ou via /config set agent ...), o
shortcoder tenta o agente escolhido primeiro; se a chamada falhar por um
motivo transiente (timeout, erro HTTP, resposta sem conteudo, parse
falho), o outro agente assume automaticamente e responde no lugar. A caixa
de resposta mostra [COUPLE] <agente> indisponivel — respondido por <outro> para deixar a troca visivel — a experiencia nunca fica sem
resposta, mas tambem nunca finge que nada aconteceu.
"Nao configurado" (por exemplo AGNES_API_KEY ausente) nao aciona o
failover — e uma falha permanente de configuracao, nao uma queda
momentanea, e mascara-la silenciosamente atras do Ornith so esconderia uma
chave esquecida. Nesse caso o erro original (AGNES_API_KEY nao configurada) continua aparecendo normalmente.
Controlado por agents.couple_enabled (bool, padrao true). Desative
com /config set agents.couple_enabled false para voltar ao comportamento
de agente unico (falha mostra o erro e para por ai).
Quando agents.agnes.tools_enabled esta habilitado (padrao), o agente Agnes
(selecionado via /agent → opcao 4, ou defaults.agent: "agnes") roda num
loop de ferramentas: o modelo decide que ferramenta chamar, o shortcoder
executa a ferramenta num job isolado (FWJOBSTART → FWJOBDONE →
FWJOBPOLL) e devolve o resultado de volta ao modelo, ate chegar na resposta
final.
As 11 ferramentas disponiveis:
| Ferramenta | Descricao |
|---|---|
read_file |
Le um arquivo dentro do workdir |
write_file |
Cria/sobrescreve um arquivo dentro do workdir |
edit_file |
Substitui a primeira ocorrencia de old_string por new_string |
bash |
Executa um comando de shell no workdir |
glob |
Lista arquivos que casam com um glob (via find) |
grep |
Busca regex nos arquivos do workdir |
list_dir |
Lista um diretorio (1 nivel) |
rag_search |
Consulta a base vetorial AdvPL/Protheus (agents.ernesto.url) |
mem0_search |
Busca memorias cross-agent (listagem por usuario) |
web_fetch |
HTTP GET de uma URL |
load_skill |
Carrega <skills_dir>/<name>/SKILL.md |
Chaves de configuracao (agents.agnes.*): tools_enabled, tools_max_tokens,
workdir, max_tool_iterations, tool_timeout, max_tool_output,
max_history, load_skills, skills_dir. Todas tem fallback no ConfigGet,
entao configs existentes funcionam sem migracao.
- Caminhos: toda ferramenta de arquivo valida o caminho com
IsWithinWorkdir(cWorkDir, cPath)antes de tocar no disco. Aceita absolutos sob o workdir e relativos (normalizados contra o workdir); rejeita..acima do workdir e absolutos fora dele. - Shell:
bash,globegrepexecutam comandos no workdir (cd). O modelo controla o comando — sao confiaveis por convencao, nao por sandbox. Limitacao de seguranca documentada. - Nenhuma ferramenta expoe rede/UI: a VM do job retorna apenas string, e o loop exibe na VM principal.
/tools selftest roda o auto-teste do harness: parse do JSON de definicoes,
IsWithinWorkdir (aceite/recusa), roundtrip write/read/edit, shell tools e
TruncateHistory. Tambem automatizado em tests/tools-tests.sh.
Orquestrador que decide qual agente usar baseado no conteudo.
Static Function DetectAgent(cInput, cDefaultAgent)
Local cLower := Lower(cInput)
// Regra 1: Comandos mem0 explicitos
If Left(cLower, 10) == "/mem0 " .Or. cLower == "/mem0 list" ...
Return "mem0"
EndIf
// Regra 2: Palavras-chave de dominio Protheus
Local aProtheusKeywords := { "campo", "tabela", "sx2", ... }
For i := 1 To Len(aProtheusKeywords)
If aProtheusKeywords[i] $ cLower
Return "ernesto"
EndIf
Next i
// Regra 3: Padrão usa agente selecionado
Return cDefaultAgentQuebra texto em linhas de tamanho maximo, respeitando palavras.
Static Function WordWrap(cText, nMaxLen)
Local aLines := {}
Local cWord, cLine := "", nWordLen, nLineLen
Local aWords, i
cText := StrTran(cText, Chr(10), " ")
cText := StrTran(cText, Chr(13), " ")
// Tokenizacao manual (StrTokArray nao existe no AdvPP)
aWords := {}
...
For i := 1 To Len(aWords)
cWord := aWords[i]
nWordLen := Len(cWord)
nLineLen := Len(cLine)
If nLineLen + nWordLen + (Empty(cLine) .And. 0 .Or. 1) <= nMaxLen
If !Empty(cLine)
cLine := cLine + " "
EndIf
cLine := cLine + cWord
Else
If !Empty(cLine)
aAdd(aLines, cLine)
EndIf
cLine := cWord
EndIf
Next i
If !Empty(cLine)
aAdd(aLines, cLine)
EndIf
Return aLinesFormata texto para caber dentro de caixa.
Static Function FormatTextForBox(cEsc, cText, nMaxLen, nBoxW)
Local aLines, cLine, cResult := "", i, nLen
aLines := WordWrap(cText, nMaxLen)
For i := 1 To Len(aLines)
cLine := aLines[i]
nLen := Len(cLine)
If nLen >= nMaxLen
cResult := cResult + cEsc + "[2;37m" + Left(cLine, nMaxLen) + cEsc + "[0m" + Chr(10)
Else
cResult := cResult + cEsc + "[2;37m" + cLine + Replicate(" ", nMaxLen - nLen) + cEsc + "[0m" + Chr(10)
EndIf
Next i
Return cResult| Endpoint | Metodo | Uso | Tempo |
|---|---|---|---|
http://127.0.0.1:11434/v1/chat/completions |
POST | Ollama LLM (local, ex: ornith-uncensored) | 1s–vários min (CPU) |
http://127.0.0.1:9081/memories/{user_id} |
GET | Mem0 list | <1s |
http://127.0.0.1:9081/memories/{user_id} |
POST | Mem0 add | <2s |
http://127.0.0.1:9081/memories/{user_id} |
DELETE | Mem0 clear | <1s |
http://127.0.0.1:9081/v1/chat/completions |
POST | Ernesto RAG | >30s |
https://apihub.agnes-ai.com/v1/chat/completions |
POST | Agnes 2.5 Flash (remoto, requer AGNES_API_KEY) |
variavel |
-
Tempo de resposta do modelo ernesto: O modelo RAG (ernesto-granite41-rag:latest) eh muito lento (>30s), causando timeout no HTTP client (timeout padrao: 30s). Modelos ollama locais pesados (ex: 9B em GPU pequena/CPU) tambem podem exigir
agents.ollama.timeoutbem maior que o padrao — vejaagents.ollama.keep_aliveno README para evitar recarregar o modelo entre mensagens. -
StrTokArray nao existe: O AdvPP nao possui a funcao
StrTokArray, entao a tokenizacao de texto eh feita manualmente noWordWrap. -
Sem streaming: As respostas sao processadas em modo nao-streaming (aguarda resposta completa).
-
Memoria limitada: O modelo
lfm25-1b-uncensoredtem contexto de 128k tokens, mas respostas longas podem ser truncadas.
A suite automatizada (tests/config-tests.sh + tests/tools-tests.sh) cobre
config write-once, aliases de /config set, /reload, o harness de
ferramentas do Agnes (selftest, IsWithinWorkdir, roundtrip write/read/edit)
e o failover do casal Agnes ↔ Ornith (fallback "nao configurado" nao
aciona failover; ambos os agentes falhando degrada graciosamente sem
travar).
Total atual: 19 testes, 19 passed, 0 failed.
Detalhes por suite: config-tests.sh roda 15 testes (inicializacao sem
config, compatibilidade retroativa de /timeout//help//history, write-once
da config, /config set com aliases, /reload, troca de agente em memoria);
tools-tests.sh roda 4 testes (selftest do harness, erro controlado do
Agnes sem AGNES_API_KEY, gating por tools_enabled, e o Test 4 novo do
casal: ambos os agentes falhando mostra o erro do primario sem travar).
# Teste 1: Inicializacao
printf '/exit\n' | ./shortcoder
# Teste 2: Ajuda
printf '/help\n/exit\n' | ./shortcoder
# Teste 3: Resposta rapida
printf '2+2\n/exit\n' | ./shortcoder
# Teste 4: Memoria
printf '/mem0 add "teste"\n/mem0 list\n/exit\n' | ./shortcoder
# Teste 5: Orquestracao
printf 'o que e o campo A1_COD?\n/exit\n' | ./shortcoder
# Testes automatizados (config + harness de ferramentas)
./tests/config-tests.sh
./tests/tools-tests.sh~/Projetos/shortcoder/
├── shortcoder # Binario standalone (build local, nao versionado)
├── shortcoder.prw # Entry point AdvPL (advplc build)
├── src/ # Fontes AdvPL do harness (sc_*.prw)
├── tests/ # config-tests.sh, tools-tests.sh
├── installer/ # Instalador Windows (NSIS)
├── README.md # Instalacao, comandos, tabela de configuracao
├── DOCUMENTACAO.md # Este arquivo
├── ARCHITECTURE.md # Arquitetura da extensao RAG (protheus-rag)
├── PROTOTIPO.md # Notas do prototipo com Mem0/LLM rapido
├── WIREFRAME.md # Wireframe da TUI
└── TEST_RESULTS.md # Relatorio historico de testes manuais
- AdvPP: https://github.com/peder1981/AdvPP
- Ollama: https://ollama.ai
- ernesto-mem0: http://127.0.0.1:9081
- MCP protheus-rag: http://127.0.0.1:9080
Codigo desenvolvido para uso interno no projeto shortcoder.