Trocar de LLM na mesma conversa (handoff)
Um dos diferenciais do Kabul: a conversa e a branch continuam iguais quando você troca de modelo, mesmo que o modelo novo rode em outro harness (Claude Code → OpenCode com Qwen local → Codex...).
Transcript canônico
crates/kabul-core/src/transcript.rs define a conversa sem depender do harness:
Transcript:entries(tudo o que aconteceu, em ordem) esegments(cada trecho rodado por um harness e modelo).Segment: harness, modelo,agent_session(id do agente, para--resume) e a primeira entrada do trecho.Entry:seq, horário, autor, segmento, checkpoint (antes da mensagem do usuário) e o tipo:- mensagem do usuário e texto do agente;
- raciocínio;
- chamada de ferramenta (entrada, saída, erro);
- aprovação (decisão);
- handoff;
- fim de turno (tokens, custo);
- aviso e erro.
Author:User { name, device },Agent { harness, model }ouSystem.
A autoria é gravada desde o começo. Na colaboração em tempo real pelo kabuld (ver colaboracao.md), cada pessoa conectada é um User com nome e device próprios. A fila única do servidor grava cada mensagem com o autor na ordem em que chegou ao agente.
Os eventos normalizados de qualquer harness (harness::Event) entram por Transcript::apply. O transcript é a fonte para o histórico, o fork, o "Reset chat", a busca e o handoff.
Fluxo da troca
O usuário escolhe no seletor um modelo de outro harness, na mesma aba.
start_segment(harness, modelo, resumo)fecha o trecho atual e grava umHandoff. A UI mostra o divisor "Trocou para X · harness Y".O Kabul monta a primeira mensagem do agente novo com
handoff_prompt:- o resumo da conversa;
- as últimas N mensagens, com autor;
- as ferramentas usadas;
- o
git diff --statcontra a base; - a branch;
- os arquivos de
.context/.
É só texto, então funciona em qualquer harness.
O agente novo roda no mesmo worktree, e os checkpoints continuam sendo gravados antes de cada mensagem.
O resumo vem, nesta ordem:
- do
/compactdo agente anterior (quando ele tiver); - de um turno curto pedindo um resumo antes da troca;
- se não houver nenhum dos dois, das últimas mensagens apenas.
Voltar para um modelo de um segmento anterior do mesmo harness usa --resume daquele segmento, mais um handoff só com o que aconteceu depois.
Fork, reset e fila usam o mesmo transcript
- Fork para nova aba:
- se for o último turno e o harness for o mesmo, usa
--resume <id> --fork-session; - nos outros casos, usa
Transcript::upto(seq)comhandoff_prompt, que é o replay.
- se for o último turno e o harness for o mesmo, usa
- Fork para novo workspace: worktree no commit do checkpoint daquele turno (
workspace_fork). O transcript do Claude é copiado para o projeto novo; nos outros harnesses, entra o replay. - Reset chat: corta as entradas a partir do
seq, opcionalmente restaura o checkpoint e o próximo envio sai com replay. - Fila: as mensagens enfileiradas são
UserMessageainda não enviadas. Editar e apagar mexem só nelas.
Estado atual
Pronto (core): tipos,
apply, segmentos com handoff,uptoehandoff_prompt, com testes.Pronto (app):
- trocar de harness no meio da conversa mostra o divisor "Trocou para X · harness Y", e o primeiro envio leva o handoff (últimas mensagens com autor, ferramentas, arquivos alterados,
.context/); - trocar de conta do mesmo provedor faz o mesmo ("Trocou para a conta X"), porque a config isolada não deixa usar
--resume; - Fork para nova aba ou workspace e Reset chat usam
--resume --fork-sessionquando dá, e replay nos outros casos.
Tudo coberto por
e2e/p0.spec.tsee2e/contas.spec.ts.- trocar de harness no meio da conversa mostra o divisor "Trocou para X · harness Y", e o primeiro envio leva o handoff (últimas mensagens com autor, ferramentas, arquivos alterados,
Próximo:
- o histórico passa a ser gravado no formato do transcript canônico (hoje grava os itens da UI e o replay é montado no frontend);
- resumo pelo
/compactantes da troca; - uso e limite por conta quando o CLI informar.
Contas (vários logins por provedor)
crates/kabul-core/src/accounts.rs: AccountProfile { provedor, apelido, email, plano, dir, padrão }. Cada perfil tem config isolada em <estado>/accounts/<provedor>/<id>, e o CLI roda com a variável dele:
- confirmadas:
CLAUDE_CONFIG_DIR,CODEX_HOME,GH_CONFIG_DIR; - agy:
HOME=<perfil>(confirmado;ANTIGRAVITY_GEMINI_DIRnão isola o token). O login rodaagy -pnum PTY: link do Google + código colado, 60 s de espera; o app reinicia o fluxo quando expira e confirma pela existência de<perfil>/.gemini/antigravity-cli/antigravity-oauth-token. - a confirmar:
XDG_DATA_HOME(OpenCode) eHOME(Qwen Code e Gemini CLI).
O "padrão do sistema" é a config normal do CLI. O login de cada perfil usa o mesmo fluxo de PTY, com o ambiente do perfil.