Assistente de codificação local: um agente que trabalha nos seus repositórios dentro de um container descartável, com cada passo transmitido ao vivo.
Você descreve uma tarefa ("corrija a função soma e rode os testes") e aponta um repositório. O Alien Code:
- sobe a Toca, um container Docker isolado, com uma cópia do repositório (o original nunca é tocado);
- coloca o agente opencode para trabalhar nela, usando um modelo aberto do Ollama;
- mostra ao vivo tudo o que o agente faz: raciocínio, comandos, saída do terminal, diffs e tokens;
- descarta a Toca no fim.
Objetivo: estudar e construir, por marcos, um agente de código no estilo do Manus e do Claude
Code Web, 100% local, seguro (sandbox) e observável (timeline de eventos). É a V2 do estudo iniciado
em poc-websocket-demo (assistente) e coding-agent (agente codificador).
flowchart LR
W["🖥️ <b>Alien Web</b><br/>a tela"]
S["👽 <b>Alien Server</b><br/>o orquestrador"]
O["🛠️ <b>opencode</b><br/>o harness"]
M["🧠 <b>Modelo</b><br/>o cérebro"]
W -- "REST + WebSocket" --> S
S -- "HTTP (comandos)<br/>SSE (narração)" --> O
O -- "API OpenAI<br/>(tokens + tool calls)" --> M
subgraph Toca ["📦 Toca (container por missão)"]
O
end
| Peça | Papel | Em uma frase |
|---|---|---|
| 🖥️ Alien Web | Tela | Abre missões e mostra a timeline ao vivo. Não decide nada. |
| 👽 Alien Server | Orquestrador | Decide o que, onde e quando: cria a Toca, manda a tarefa, grava os eventos, controla o status. Nunca fala com o modelo. |
| 🛠️ opencode | Harness | Decide como fazer a tarefa: conversa com o modelo, executa as tools (bash, edit…) e narra cada passo. |
| 🧠 Modelo | Cérebro | Só recebe texto e devolve texto ou um pedido de tool. Não sabe que o resto existe. |
O modelo joga, o opencode narra e o Alien Server anota cada lance (um
AlienEventnumerado) e repassa ao navegador.
Pré-requisitos: Docker (com Compose), Java 21, Maven, Node 20 e git.
# 1. imagem da Toca (de novo sempre que toca/ mudar)
docker build -t alien/toca:0.1 toca/
# 2. Ollama + rede das Tocas (alien-net)
docker compose up -d ollama
docker compose exec ollama ollama pull qwen3:8b
docker compose exec ollama ollama create qwen3-8b-t10 -f /modelfiles/qwen3-8b-t10.Modelfile
# 3. Alien Server → 127.0.0.1:8080
cd alien-server && mvn spring-boot:run
# 4. Alien Web → http://127.0.0.1:5173 (/api e /ws vão para o servidor pelo proxy do Vite)
cd alien-web && npm install && npm run devNa tela: Nova missão → descreva a tarefa e informe o caminho do repositório → Abrir missão.
Quando o agente termina, a missão fica AGUARDANDO REVISÃO com o patch no cartão Entrega:
Aplicar cria a branch alien/<missão> no seu repositório (sua branch atual e seus arquivos
não são tocados); Descartar não leva nada.
Configuração (alien-server/src/main/resources/application.yml)
O application.yml é dividido em cinco seções: 1. Modelo · 2. Missões · 3. Toca ·
4. Workspace · 5. Servidor. O padrão funciona sem chave nenhuma, com o Ollama do
compose.yaml.
Trocar de modelo é como no opencode: cada provedor compatível com a API da OpenAI é um bloco
em alien.model.providers, e alien.model.default escolhe provedor/modelo. Para não mexer no
repositório, coloque isso em ~/.alien-code/alien.yml, que é lido por cima do padrão:
alien:
model:
default: openrouter/qwen/qwen3-coder
providers:
openrouter:
name: OpenRouter
base-url: https://openrouter.ai/api/v1
api-key: ${OPENROUTER_API_KEY} # a chave vem do ambiente
models:
- qwen/qwen3-coder| Propriedade | Padrão | Para quê |
|---|---|---|
alien.model.default |
ollama/qwen3-8b-t10 |
Modelo das missões (provedor/modelo) |
alien.model.providers.<id>.base-url |
http://ollama:11434/v1 |
Provedor visto de dentro da Toca |
alien.model.providers.<id>.api-key |
(vazia) | Chave do provedor; use ${VARIAVEL} |
alien.model.providers.<id>.models |
qwen3-8b-t10, qwen3:8b |
Modelos oferecidos ao agente |
alien.model.request-timeout |
30m |
Tempo máximo de uma chamada ao modelo |
alien.mission.task-timeout |
45m |
Tempo máximo de uma tarefa |
alien.mission.keep-toca |
false |
Manter a Toca no fim (para depurar) |
alien.toca.memory · cpus · ttl |
6GB · 4 · 60m |
Limites da Toca |
alien.workspace.allowed-roots |
~ |
Onde os repositórios podem estar |
Sem GPU é lento. Numa CPU de notebook (i7-1255U), o
qwen3:8bprocessa ~19 tokens/s e gera ~4 tokens/s. Só o system prompt do opencode tem ~7k tokens, e uma tarefa simples levou 16 min. Com GPU, ou com um Ollama em outra máquina (alien.model.providers.ollama.base-url), o mesmo fluxo roda em segundos.
API (REST e WebSocket)
curl -s -X POST localhost:8080/api/missions -H 'Content-Type: application/json' -d '{
"prompt": "A função soma em calc.py está errada. Corrija e rode um teste rápido.",
"seed": {"type": "existing", "repositories": [{"path": "'$HOME'/projetos/calc"}]}
}'
# → {"id":"m-3f9a1c2e","status":"CREATED",...}
websocat 'ws://127.0.0.1:8080/ws/missions/m-3f9a1c2e?lastSeq=0' # um envelope JSON por evento| Rota | O que faz |
|---|---|
POST /api/missions |
Abre a missão (201) e a conduz em segundo plano |
GET /api/missions · GET /api/missions/{id} |
Lista / snapshot com lastSeq |
POST /api/missions/{id}/stop |
Para a missão |
GET /api/missions/{id}/delivery |
Entrega: patch completo, arquivos e estado |
POST /api/missions/{id}/delivery/approve · /reject |
Aplica numa branch alien/<missão> (409 se a branch já existe) · descarta |
WS /ws/missions/{id}?lastSeq=N |
Eventos com seq > N e depois ao vivo; aceita {"type":"stop"} |
POST /api/tocas |
Cria só uma Toca, sem missão (útil para depurar a semeadura) |
GET /api/tocas · GET /api/tocas/{id} · DELETE /api/tocas/{id} |
Lista / detalha / descarta |
Projeto novo em vez de repositório: "seed": {"type": "new", "name": "meu-projeto"}.
A senha do opencode de cada Toca nunca aparece na API.
Testes
cd alien-web && npm test # reducer da timeline, reconexão com lastSeq, renderização
cd alien-server && mvn test # unitários (domínio, casos de uso, adaptadores com git e HTTP reais)
cd alien-server && mvn verify # + integração com Tocas reais (precisa da imagem e da rede alien-net)O MissionLifecycleIT roda missões completas com o opencode de verdade numa Toca de verdade,
mas com um LLM roteirizado (ScriptedLlmServer) no lugar do Ollama: em segundos e sem GPU.
O servidor segue Clean Architecture com dois domínios. O core tem as regras e não conhece
tecnologia; os adapters implementam as portas do core (Docker, git, opencode, SQLite, HTTP).
alien-code/
├── alien-server/ 👽 Java 21 + Spring Boot
├── alien-web/ 🖥️ React + TypeScript + Vite
├── toca/ 📦 imagem Docker da Toca (JDK, Maven, Node, git, opencode)
├── ollama/ 🧠 Modelfile do qwen3-8b-t10
├── docs/ 📄 especificação, diagramas de sequência, contrato do opencode
└── compose.yaml Ollama + rede alien-net
Cria, semeia, vigia e descarta os containers onde o agente trabalha.
| Camada | Classes | O que é |
|---|---|---|
domain/model |
Toca, TocaId, TocaStatus, TocaEndpoint, Seed, RepositorySeed |
A Toca, o endereço do opencode nela e o que vai dentro (repos ou projeto novo) |
port |
SandboxPort, WorkspaceSnapshotPort, AgentHarnessPort, TocaRepository |
O que o core precisa: container, cópia dos repos, harness saudável, guardar Tocas |
application |
ProvisionTocaService, DisposeTocaService, ListTocasService, ReapTocasService |
Criar + semear + esperar o opencode; descartar; listar; faxina por TTL |
adapters |
DockerSandboxAdapter, GitCloneSnapshotAdapter, OpencodeHealthAdapter, OpencodeConfigFactory, InMemoryTocaRepository, TocaJanitor, TocaController |
Docker, git clone --local, GET /global/health, config do opencode, memória, agendador, REST |
Conduz uma missão do início ao fim e transforma o que o agente faz em eventos.
| Camada | Classes | O que é |
|---|---|---|
domain/model |
Mission, MissionId, MissionStatus |
O pedido e em que pé está (a foto): CREATED → PROVISIONING → EXECUTING → COMPLETED / FAILED / CANCELLED |
AgentModel |
Qual LLM (provedor/modelo), um valor dentro da missão |
|
AlienEvent, NewEvent, EventType, EventSource |
O que aconteceu (o filme): uma linha numerada (seq) da timeline |
|
port |
AgentSessionPort + AgentEvent, EventStorePort, MissionRepository |
Falar com o harness (neutro, sem formato do opencode); gravar eventos e missões |
application |
MissionConductor |
O orquestrador: Toca → sessão → prompt → espera → fim |
TaskTimeline |
AgentEvent → linhas da timeline (NewEvent) |
|
MissionEventHub |
Grava no Event Store (ganha seq) e entrega a quem assiste, sem buraco nem repetição |
|
StartMissionService, StopMissionService, GetMissionService, FailInterruptedMissionsService |
Casos de uso | |
adapters |
OpencodeSessionAdapter, OpencodeEventTranslator |
HTTP + SSE com o opencode; JSON do opencode → AgentEvent |
MissionWebSocketHandler, MissionController |
WebSocket (replay + ao vivo) e REST | |
SqliteEventStore, SqliteMissionRepository, MissionRecovery |
SQLite (~/.alien-code/alien.db) e recuperação na subida |
modelo → opencode ──SSE──▶ OpencodeSessionAdapter → OpencodeEventTranslator → TaskTimeline
→ MissionEventHub (seq) → SqliteEventStore + MissionWebSocketHandler ──WS──▶ Alien Web
| Pasta | O que tem |
|---|---|
domain/ |
Envelope AlienEvent e o reducer puro da timeline (eventos → passos) |
api/ |
REST (missionsApi) e MissionSocket (reconecta sozinho com lastSeq) |
hooks/ |
useMissionTimeline |
components/ |
App, NewMissionForm, MissionList, MissionView, Timeline, TerminalPanel, DiffPanel, StatusBadge |
docs/sequencias.md: cada fluxo desenhado classe a classe.docs/alien-code-especificacao.pdf: a especificação completa.docs/referencias/opencode-1.18.33-openapi.json: contrato real da API do opencode.
Mapeamento real dos eventos do opencode (atualiza a seção 8.3 da especificação)
O OpenAPI do opencode 1.18.33 declara duas gerações de eventos (message.part.* e
session.next.*), mas o servidor emite só a primeira. Conferido gravando o SSE de sessões reais
(alien-server/src/test/resources/opencode/).
| SSE do opencode | AgentEvent |
Evento Alien |
|---|---|---|
message.part.delta de parte reasoning |
ReasoningDelta |
thinking.delta |
message.part.delta de parte text |
TextDelta |
assistant.delta |
message.part.updated tool running |
ToolStarted |
tool.started |
message.part.updated tool completed / error |
ToolFinished |
tool.completed (+ terminal.output se bash) |
idem, com metadata.filediff |
FileChanged |
file.changed |
message.part.updated parte step-finish |
ModelCallFinished |
budget.updated (acumulado) |
session.error MessageAbortedError |
SessionFailed(aborted) |
fim da tarefa por Parar |
session.error (outros) |
SessionFailed |
step.failed + missão FAILED |
session.idle |
SessionIdle |
step.completed |
O message.part.delta traz só o id da parte; o tipo vem do message.part.updated anterior, por isso
o tradutor guarda o tipo de cada parte.
Diferenças conscientes em relação à especificação (M0 e M1)
- Rede da Toca:
alien-netainda é uma bridge comum, criada pelocompose.yaml. O bloqueio de saída chega no M2, com o proxy (uma redeinternalnão permite publicar a porta do opencode). - Limite de processos: 512 em vez de 256; opencode + Node + Maven passam de 256.
- Persistência: missões e eventos em SQLite; Tocas em memória. Missões ativas durante um reinício
viram
FAILEDna subida seguinte. - Alterações não commitadas do repositório original ainda não vão para a Toca.
- Permissões do agente:
editebashliberados,webfetchnegado. Cartões de aprovação no M2. - Saídas grandes:
outputepatchcortados em 16 mil caracteres (truncated: true); blobs no M2. - Alien Web: React 19 e Vitest 4 (a especificação cita React 18).
- Configuração do opencode: entregue em
OPENCODE_CONFIG_CONTENT, gerada dealien.model.*.
| Marco | O que entrega | |
|---|---|---|
| ✅ | M0 · Esqueleto | Imagem da Toca com opencode; o Alien Server cria, semeia, vigia e destrói Tocas com limites de CPU, memória e processos |
| ✅ | M1 · Timeline ao vivo | Missão com 1 repo e 1 tarefa; eventos do opencode → Event Store → WebSocket → UI, com replay por lastSeq e botão Parar |
| 🚧 | M2 · Entrega segura | ✅ patch revisado na UI e aplicado numa branch local · ⏳ gitleaks, verificação pelo orquestrador e halting, aprovações de comandos, rede bloqueada + proxy de pacotes, blobs para saídas grandes |
| ⏳ | M3 · Grafo | Grafo de dependências local (graphify) consultado pelo agente via MCP; impacto da mudança, testes afetados e diff estrutural da entrega |
| ⏳ | M4 · AI-DLC completo | Fluxo completo com perfis e portões de aprovação (aidlc-workflows) |