Orkas Orkas
Início Blog Arquitetura
Arquitetura

A camada que transforma um modelo em um produto: Agent Harness da engenharia Orkas

Como Orkas transforma chamadas de modelo em um tempo de execução confiável de agente desktop: loops de execução de streaming, roteamento de ferramentas, compactação de contexto, abstração de provedor, memória e sessões seguras contra falhas.

Qualquer pessoa que tenha enviado um produto para agente conhece a sensação: fazer uma demonstração funcionar é rápido, mas transformá-la em algo em que o usuário confia todos os dias — algo que funciona o dia todo em sua própria máquina sem cair — é difícil. E a parte difícil não é conectar o modelo. É toda a camada que fica ao redor do modelo.

Essa camada tem alguns nomes; o que usarei é o Agent Harness. Ele fica entre o "grande modelo" e os "recursos do produto" e é o verdadeiro tempo de execução: transforma uma única solicitação do usuário em rodada após rodada de conversa com o modelo, encadeando chamadas de ferramenta entre elas, alimentando os resultados, compactando o contexto antes que ele transborde, tentando novamente através de soluços de rede e recuperando a conversa mesmo após o processo travar. O modelo pensa; o arnês faz com que esse pensamento se transforme em uma sequência confiável de ações.

Orkas é um aplicativo de agente de desktop executado na própria máquina do usuário e seu funcionamento fica inteiramente no cliente. Este artigo explica como essa camada é construída: como ela é dividida em camadas, como é o loop de execução, como as ferramentas e os modelos são abstraídos e como a memória e as sessões são tratadas. Os detalhes do código foram eliminados e generalizados, mas a estrutura de engenharia é real.

Em resumo O harness é a parte que você realmente instala Tudo descrito aqui vai no app desktop — a mesma camada que roda seus agentes localmente, com o código no GitHub.
Baixar o Orkas — grátis

As camadas

Coloque um produto de agente na horizontal e você obterá aproximadamente estas camadas, empilhadas de baixo para cima:

┌───────── ────────────────────────────────┐
│ Recursos do produto (chat/habilidades/conectores/sincronização) │
├──────────────────── ─────────────────────┤
│ Agente Harness (loop de execução / ferramentas / sessão) │
├──────────────────── ─────────────────────┤
│ Abstração de provedor (unificar muitos fornecedores LLM) │
├──────────────────── ─────────────────────┤
│ Infraestrutura (tipos/erros/registro/configuração) │
└─────────────────────────── ──────────────┘

Há uma escolha importante aqui: toda a inferência do modelo acontece no cliente. O aplicativo de desktop não é um thin client – ​​ele segura o chicote e chama o modelo diretamente. O servidor lida apenas com contas, sincronização de vários dispositivos e cobrança; ele não executa o agente. Essa decisão moldou quase tudo no downstream: as sessões chegam ao disco local, as ferramentas operam diretamente no diretório de trabalho do usuário e os dados confidenciais nunca saem da máquina.

O chicote em si se divide em algumas partes: o loop de execução (executor), a sessão, as ferramentas, a camada do Provedor e a memória. Vamos analisá-los um de cada vez.

O loop de execução: um gerador de streaming

O coração do arnês é o corredor. Em uma frase, o que ele faz é: falar com o modelo repetidamente até que ele diga "Terminei".

Ele é implementado como um gerador assíncrono e essa escolha é importante. A execução de um único agente é muito mais do que "enviar uma solicitação e aguardar um resultado". Muita coisa acontece nesse meio tempo - o modelo está emitindo tokens, quer chamar uma ferramenta, a ferramenta foi finalizada, o contexto demorou o suficiente para acionar a compactação, a rede falhou e estamos tentando novamente. Com retornos de chamada ou promessas simples, esses estados intermediários são difíceis de serem apresentados de forma clara ao chamador. Como geradores, todos eles se tornam um fluxo de eventos gerados por yield:

tipo AgentRunEvent =
  | { tipo: "texto_delta"; text: string } // modelo emitindo tokens
  | { tipo: "tool_start"; nome: sequência; input: desconhecido } // uma ferramenta começa a ser executada
  | { tipo: "tool_end"; nome: sequência; resultado: string } // uma ferramenta finalizada
  | { tipo: "compactação"; tokensAntes: número; tokensAfter: number } // contexto compactado
  | { tipo: "tentar novamente"; tentativa: número; razão: string } // erro, tentando novamente
  | { tipo: "concluído"; resultado: AgentRunResult } // terminal

A IU se inscreve nesse fluxo de eventos e pinta a saída do modelo e a execução da ferramenta em tempo real. O ponto de entrada sem streaming é internamente apenas "consumir o stream até o fim, fazer o feito" — ambos os pontos de entrada compartilham uma implementação, portanto não há um segundo caminho de código para ficar fora de sincronia.

O que acontece dentro de uma curva

Desenrolado, um turno fica mais ou menos assim:

  1. Envie a mensagem do usuário (possivelmente com imagens) para o histórico da sessão.
  2. Monte o prompt do sistema, injetando as ferramentas atualmente disponíveis, o índice de habilidades e assim por diante.
  3. Analise a string do modelo e resolva-a para um provedor concreto e um ID de modelo.
  4. Converta todas as ferramentas em definições que o modelo entenda e envie-as junto com o histórico.
  5. Consuma o fluxo de resposta do modelo, produzindo-ing token de texto por token enquanto coleta quaisquer chamadas de ferramenta feitas pelo modelo.
  6. Quando o fluxo terminar, observe o motivo de parada do modelo:
  • Se for tool_use, o modelo quer chamar uma ferramenta - execute as ferramentas, depois volte para a etapa 5 e pergunte ao modelo novamente.
  • Caso contrário, a virada acabou - monte o resultado, rendimento feito e retorne.

Há uma invariante que você deve manter aqui: cada chamada de ferramenta que o modelo faz deve ser imediatamente seguida no histórico por um resultado de ferramenta correspondente. A API do modelo impõe esse emparelhamento com força – interrompa-o e a próxima solicitação apresentará um erro ou simplesmente travará. Voltaremos a isso quando falarmos sobre a autocura da sessão.

Como uma chamada de ferramenta é encaminhada de volta

O modelo não executa ferramentas por si só; diz apenas "Gostaria de chamar read_file com esses argumentos." Assim que o executor captar essa intenção:

for (chamada const de toolUseBlocks) {
  rendimento {tipo: "tool_start", nome: call.name, entrada: call.input };

  ferramenta const = this.tools.get(call.name);
  const ctx = {workingDir, sinal, estado: { sandboxEnv } };
  resultado const = aguardar ferramenta.execute(call.input, ctx);

  // acrescenta o resultado à sessão como uma mensagem de resultado da ferramenta
  session.addToolResult(call.id, resultado);

  rendimento {tipo: "tool_end", nome: call.name, resultado: resultado.content };
}

As ferramentas são executadas sequencialmente, os resultados são gravados no histórico na ordem em que o modelo os declarou e, em seguida, o modelo é questionado novamente com esses resultados em mãos. Vendo os resultados, o modelo pode chamar outra ferramenta ou apenas dar a resposta final. Esse ciclo "perguntar → ligar → atender → perguntar novamente" é exatamente o que permite que um agente conclua tarefas de várias etapas.

Um detalhe merece destaque: algumas ferramentas retornam imagens — capturas de tela, imagens geradas. Mas muitos modelos não aceitam imagens no canal ferramenta-resultado. Orkas lida com isso dividindo a imagem em uma mensagem de usuário separada colocada após o resultado da ferramenta — o modelo primeiro lê "a ferramenta retornou este texto" e, no turno seguinte, vê a imagem correspondente. Um pequeno compromisso que contorna as diferenças de capacidade entre os provedores.

O que fazer quando o contexto está prestes a transbordar

A tarefa mais longa atingida com mais frequência é a janela de contexto. Orkas não espera até que esteja cheio — ele define uma marca d’água de 60%: após cada rodada de ferramentas, ele estima quanto da janela os tokens atuais ocupam e, quando essa marca passa de 60%, ele aciona proativamente a compactação.

A própria compactação pede ao modelo para resumir a conversa anterior e, em seguida, substitui as mensagens antigas por esse resumo, mantendo apenas o final mais recente. Parece simples, mas há uma armadilha: após a troca, a cauda retida não deve começar com um "resultado de ferramenta órfã" - não pode haver um "resultado sem chamada correspondente" ou você quebrou o invariante de emparelhamento novamente. Portanto, a lógica de compactação garante que o corte fique em um limite limpo.

Há uma escolha mais interessante que vale a pena analisar aqui: por que a abordagem grosseira de "resumar todo o bloco em 60%", em vez de algo mais refinado - pontuar cada mensagem e cortar por importância, extração estruturada de saídas de ferramentas, manutenção de uma árvore de memória em camadas? Essas abordagens parecem ótimas nos artigos, mas deliberadamente não seguimos esse caminho, por três motivos.

Primeiro, armazenamento em cache. O cache de prompt do modelo atinge por prefixo: desde que o prefixo do histórico permaneça inalterado, esse intervalo atinge o cache, economizando dinheiro e latência. A compactação refinada reescreve constantemente o meio do histórico, o que significa quebrar repetidamente o prefixo em cache – cada edição força um grande repreenchimento. A estratégia "deixe como está e depois compacte uma vez na marca d'água" mantém o prefixo estável na grande maioria das curvas, com apenas uma única compactação invalidando-o. Muito mais amigável para o cache.

Em segundo lugar, complexidade. Essa invariante de que “cada chamada de ferramenta deve ser emparelhada” continuamos martelando – quanto mais detalhadamente você aparar o histórico, maior será a probabilidade de quebrá-lo em algum canto. Um resumo grosseiro só precisa proteger um ponto de corte limpo; há uma ordem de magnitude menos lugares onde errar. Uma classe a menos de caso extremo é uma classe a menos de incidente de produção.

Terceiro, aproveitar os dividendos de modelos melhores. As janelas de contexto têm crescido continuamente nos últimos anos, e os modelos lidam cada vez melhor com contextos longos. Investir esforços hoje em um algoritmo de compactação elaborado é essencialmente lutar contra um problema que está diminuindo – é provável que você termine de ajustá-lo no momento em que a próxima geração dobra sua janela e sua complexidade se torna pura responsabilidade. Por outro lado, entregar o resumo ao próprio modelo fica automaticamente melhor à medida que o modelo melhora: quanto melhor ele for em escolher o que importa, maior será a qualidade do resumo, e não mudamos uma linha. A complexidade que o modelo pode carregar para você é uma complexidade que você não deveria carregar sozinho.

A estimativa de token esconde um problema fácil de ignorar: o chinês. Estime o chinês usando as intuições do inglês (aproximadamente um token para cada poucos caracteres) e você subestimará muito. Orkas pondera os caracteres CJK separadamente em sua estimativa; caso contrário, a marca d'água de uma conversa totalmente em chinês será incorreta e a compactação não será acionada quando deveria.

Erros e novas tentativas

Executando na máquina do usuário e dependendo de um modelo de API externo, os erros são a norma, não a exceção. O executor os classifica em algumas classes e trata cada uma de maneira diferente:

  • Repetivel: limites de taxa, tempos limite, queda de conexões, 5xx. Backoff exponencial com jitter, limitado a 30 segundos; se for um limite de taxa e o servidor enviou retry-after, respeite-o.
  • Não é possível repetir: coisas como falhas de autenticação - nenhum número de tentativas ajudará, então elimine o erro imediatamente.
  • Especial: estouro de contexto. Tente compactar primeiro, tente novamente uma vez depois e só erre se ainda falhar.

Há mais uma classe: "a própria ferramenta falhou." Isso não afunda o turno inteiro - uma falha na ferramenta é em si uma informação para o modelo, que, vendo "aquele comando errado", pode perfeitamente tentar uma abordagem diferente. O chicote distingue esses erros transitórios da ferramenta de falhas reais: ele não interrompe o fluxo nem os perde – eles aparecem em estatísticas posteriores. (Esses dados alimentam posteriormente o mecanismo de autoevolução, que é o assunto do próximo artigo.)

O sinal de cancelamento externo (AbortSignal) é verificado em cada ponto-chave. O usuário clica em "parar" e a curva atual é interrompida imediatamente. Nenhuma nova tentativa é iniciada.

Abstração de ferramenta: simples o suficiente para ser estendida

A interface da ferramenta é deliberadamente fina:

interface AgentTool {
  nome somente leitura: string;
  descrição somente leitura: string;          // mostrado ao modelo
  somente leitura inputSchema: Record<string, desconhecido>;  // Esquema JSON para restringir entradas
  execute(entrada: Record<string, desconhecido>, ctx: ToolContext): Promise<ToolResult>;
}

Uma ferramenta é apenas “um nome + uma descrição para o modelo + um esquema de entrada + uma função de execução”. Os recursos integrados – ler arquivo, gravar arquivo, executar um comando shell, pesquisar e buscar na web – todos implementam essa interface. A camada da área de trabalho empilha um lote de ferramentas com sabor local no topo (pesquisa na base de conhecimento, geração de imagens, chamada de conectores externos), mas a interface é a mesma.

A vantagem de uma interface fina é que a origem de uma ferramenta é irrelevante para o executor: integrada, definida pelo usuário ou carregada a partir de uma habilidade. São todas o mesmo tipo de coisa, registradas em um Map<string, AgentTool> e convertidas em definições legíveis pelo modelo a cada turno.

Ferramentas de efeito colateral, como comandos shell, passam por um executor isolado: tempos limite, limites de comprimento de saída, uma lista de bloqueio de comandos e variáveis ​​de ambiente passadas separadamente, em vez de alterar o ambiente global do processo.

A camada Provedor: nivelando muitos modelos em uma interface

As preferências de modelo dos usuários estão em todo o mapa, e um produto não pode ser vinculado a um fornecedor. Abaixo do equipamento, Orkas estabelece uma abstração de Provedor que unifica modelos de diferentes fornecedores por trás de uma única interface:

interface LLMProvider {
  id somente leitura: string;
  complete(params: CompletionParams): Promise<CompletionResult>;
  stream(params: CompletionParams): AsyncIterable<StreamEvent>;
  validarAuth(): Promessa<boolean>;
}

O executor acima só conversa com esta interface; não tem ideia de qual fornecedor está por trás disso. Um registro trata o roteamento por string de modelo: um formulário provedor/modelo explícito é dividido diretamente; um nome de modelo simples é atribuído por prefixo. Auth (uma chave de API ou token OAuth) também é gerenciado aqui, e um token OAuth expirado é atualizado automaticamente.

Achatando muitos modelos, a verdadeira dor de cabeça não é a conclusão do texto, mas sim os cantos onde a semântica dos fornecedores discorda. Dois exemplos que nos marcaram.

Uma delas é preservar bloqueios de pensamento entre os fornecedores. Os modelos de raciocínio emitem uma série de conteúdos “pensantes”; alguns fornecedores o criptografam e exigem que você o reproduza literalmente, outros o representam com um conjunto diferente de campos. Se um usuário mudar do fornecedor A para o fornecedor B no meio da conversa, a assinatura desse intervalo de pensamento no histórico não corresponderá mais. A solução é carimbar cada mensagem no histórico com “qual modelo produziu isso”, para que a camada de transformação possa decidir se deseja mantê-la literalmente: mesmo modelo, mantenha-o; modelo diferente, faça downgrade de acordo com as regras.

O outro é o cache de prompts. Nos turnos de uma sessão, o prefixo é altamente repetitivo e o armazenamento em cache economiza custos e latência significativos. A implementação passa o ID da sessão como a chave de cache para os fornecedores que o suportam, lidando com os limites de comprimento de chave de cada fornecedor ao longo do caminho (truncar ou hash se for muito longo, por exemplo).

Tudo isso é trabalho pesado, mas é precisamente essa camada de trabalho pesado que permite ao corredor acima fingir que "só existe um tipo de modelo".

Memória: dois mecanismos, cada um para sua função

A "memória" no Orkas são na verdade dois mecanismos paralelos que resolvem dois problemas completamente diferentes. Uma delas é uma base de conhecimento baseada em recuperação, para o material em massa que você “procura quando necessário”. A outra é a memória entre sessões, para o pequeno conjunto de fatos importantes que você “deve sempre ter em mente”. Muitos produtos misturam esses dois; mantê-los separados torna as coisas muito mais claras.

Base de conhecimento: recuperação híbrida

O primeiro mecanismo visa conteúdo grande, mas apenas ocasionalmente relevante – documentos do usuário, notas anteriores, conhecimento do domínio. Esta é uma base de conhecimento local com recuperação de vetores, em dois backends: uma versão leve de memória pura (para testes e uso efêmero) e uma versão persistida em um banco de dados local (para produção, com indexação de texto completo e vetores).

Os dados chegam por este caminho:

documentos → pedaços de limites de linha (com sobreposição) → indexação dupla
                                          ├─ índice de texto completo (palavras-chave, sem custo de incorporação)
                                          └─ índice vetorial (se um modelo de incorporação estiver configurado)

Os pedaços são cortados nos limites das linhas com uma pequena sobreposição entre eles, para evitar cortar um pedaço completo de significado ao meio. A recuperação é híbrida: uma passagem de vetor (semanticamente próxima) e uma passagem de palavra-chave (acertos literais), com os dois conjuntos de resultados mesclados via RRF (Reciprocal Rank Fusion):

pontuação = Σ 1 / (k + rank_i)

Quanto mais alta a classificação de um resultado em uma passagem, mais ele contribui; somados nas duas passagens, vocês respeitam a relevância semântica e não perdem correspondências literais exatas. Os pesos do vetor e das palavras-chave são ajustáveis, por padrão para favorecer a semântica. Após a mesclagem, os resultados são desduplicados por "(documento, linha inicial)", mantendo apenas o melhor por local e, em seguida, qualquer coisa abaixo de um limite é cortada, retornando o K superior.

Por que não confiar apenas nos vetores? Porque a recuperação de vetores geralmente se baseia em nomes próprios, símbolos de código e strings literais exatas - consultas que não são semanticamente especiais, mas onde o literal é muito importante - enquanto apenas palavras-chave não conseguem capturar "mesmo significado, fraseado diferente". Executar ambos é uma troca muito prática entre qualidade e custo de recuperação.

Memória entre sessões: mantendo o usuário em mente

A base de conhecimento resolve "muito material para armazenar". Mas há outro tipo de coisa – pequeno em volume, mas que deve estar sempre em mente: quem é esse usuário, o que ele prefere, o que foi acordado da última vez. Eles não deveriam depender de recuperação para "ter sorte e recall" — eles deveriam estar presentes em todos os turnos.

Para isso, Orkas cria uma camada separada de memória entre sessões, dividida por conteúdo em duas partes:

  • Perfil do usuário: fatos estáveis ​​sobre a pessoa — função, preferências, estilo de comunicação, pilha de tecnologia.
  • Notas de fatos: fatos duráveis ​​sobre o trabalho — decisões, marcos, convenções do projeto.

Ambos são pequenos, cada um com um limite rígido de alguns milhares de caracteres, o que os obriga a manter apenas o que é genuinamente útil a longo prazo. Eles não passam por recuperação; em vez disso, eles são congelados diretamente no prompt do sistema no início de cada turno – o que significa que o agente simplesmente “sabe” essas coisas, sem ter que se lembrar de procurá-las. Essa é exatamente a postura oposta da base de conhecimento: a base de conhecimento é “buscada somente quando necessário, seguida”, a memória entre sessões é “sempre presente, sempre visível”.

As gravações passam por uma ferramenta de memória dedicada que o modelo chama quando julga, no meio da conversa, que "vale a pena lembrar disso a longo prazo", suportando adição, substituição de substring e exclusão. O que salvar e o que não salvar está claramente escrito na descrição da ferramenta: as correções e preferências do usuário são prioridade máxima; decisões e convenções duradouras são salvas; enquanto o estado transitório da tarefa atual, informações de depuração únicas e qualquer coisa facilmente redescoberto não o fazem - a memória é para "fatos duráveis ​​sobre o usuário e o projeto", não para "onde cheguei neste momento".

Há um detalhe fácil de ignorar, mas muito importante: uma verificação de segurança é executada antes de cada gravação. Esse conteúdo entra no prompt do sistema literalmente e persiste durante as sessões por um longo tempo – efetivamente uma superfície de injeção durável. Assim, cada memória prestes a ser gravada no disco é primeiro verificada em busca de padrões suspeitos - frases clássicas de injeção de prompt ("ignorar todas as instruções anteriores" e similares), comandos que tentam exfiltrar chaves, caracteres unicode invisíveis escondidos no texto - e uma correspondência é rejeitada imediatamente. Com desduplicação e corte acima do limite, essa camada de memória permanece útil sem se tornar um risco.

Juntos, os dois mecanismos cobrem ambos os fins — "enorme, mas ocasional" e "pequeno, mas constante": a base de conhecimento lida com o primeiro, e a memória de sessão cruzada, com o segundo. Acrescente a isso a compreensão que o agente tem de si mesmo (o assunto do próximo artigo), e um agente Orkas entra carregando três tipos de memória ao mesmo tempo: sobre o material, sobre o usuário e sobre si mesmo.

Sessões: criadas para travar, criadas para curar

Uma sessão gerencia o histórico de mensagens. A versão básica é apenas um conjunto de mensagens na memória com recorte e compactação do histórico. Mas qualquer coisa em execução na máquina de um usuário deve presumir que pode ser encerrada a qualquer momento – o usuário sai do aplicativo, o sistema é reinicializado, um tempo limite de watchdog interrompe o processo. Portanto, a produção usa uma sessão persistente, gravada em um arquivo JSONL local, uma mensagem por linha.

Existem duas estratégias de gravação: anexar uma nova mensagem usa um acréscimo atômico; qualquer coisa que reescreva o arquivo inteiro (compactação, limpeza) usa "escrever um arquivo temporário + renomeação atômica". Dessa forma, mesmo que a energia seja cortada no meio da gravação, você nunca deixa para trás metade de um registro corrompido.

A parte mais interessante é curar chamadas de ferramentas órfãs. Voltando à invariante de emparelhamento: o modelo faz uma chamada de ferramenta, o chicote a executa, o resultado é gravado de volta - interrompa qualquer uma dessas três etapas e você deixa um órfão no disco, "uma chamada sem resultado". Carregue essa sessão na próxima vez e envie-a como está para o modelo, e a API a rejeitará ou travará.

A lógica de recuperação é executada sempre que uma sessão é carregada do disco e é idempotente:

  1. Verifique todas as mensagens do assistente e colete os IDs de chamada de ferramenta que eles fizeram.
  2. Aguarde os resultados da ferramenta correspondentes.
  3. Para qualquer chamada sem resultado correspondente, sintetize uma chamada marcada como "interrompida".
  4. Ao longo do caminho, alinhe a ordem dos resultados com a ordem da declaração de chamada e descarte quaisquer resultados órfãos que não tenham chamada correspondente.

Após essa passagem, é garantido que a sessão esteja em um estado que atenda aos requisitos de emparelhamento da API e seja segura para envio. O mecanismo parece normal, mas é a rede de segurança que mantém "a conversa de um usuário não trava permanentemente apenas por causa de uma falha".

Algumas decisões que importaram em retrospectiva

Juntando tudo isso, algumas decisões parecem especialmente valiosas após o fato.

Geradores como interface principal. Streaming e não streaming compartilham uma implementação, o estado intermediário surge naturalmente e a UI pode pintar quantos detalhes desejar. Isso salvou toda uma classe de bugs de inconsistência que teriam criado "implementar o não streaming primeiro e ativar o streaming depois".

Compacta a 60%, não quando estiver cheio. Isso deixa espaço para a compactação em si (o que também custa uma chamada de modelo) e evita embaralhamento no último momento.

A invariante de emparelhamento percorre tudo. Do ponto de corte da compactação à gravação no disco e à recuperação no tempo de carregamento, todos os locais que tocam a sessão mantêm a mesma regra. Com uma regra, nenhum spot precisa inventar sua própria lógica de correção.

Trabalho pesado concentrado na camada Provedor. Toda a estranheza entre fornecedores — bloqueios de pensamento, chaves de cache, diferenças de capacidade — é digerida nesta camada, em troca de um executor limpo acima. Adicione um novo fornecedor de modelo algum dia e a mudança quase não se espalhará.

Concluindo

O equipamento de Orkas não possui algoritmo impressionante. Seu valor está em "fazer um agente funcionar de maneira confiável em um ambiente real" e dividi-lo em um conjunto de módulos com limites claros, cada um possuindo uma fatia: o executor possui o loop e as novas tentativas, as ferramentas possuem a capacidade, a camada do Provedor possui o nivelamento de muitos modelos, a memória possui a recuperação, a sessão possui a persistência e a cura. Nenhum deles é complexo por si só; somente juntos eles sustentam algo que as pessoas usam todos os dias.

Se há algo a tirar: faça do loop de execução um gerador de streaming e o estado intermediário fica muito mais fácil de manusear; uma vez que um invariante principal seja definido (como "chamadas de ferramentas devem ser emparelhadas"), mantenha-o consistentemente durante a compactação, gravações em disco e carregamento - não deixe nenhum canto ser a exceção; concentre o trabalho pesado entre fornecedores em uma camada e mantenha-o fora da lógica de negócios; e - o mais claro de tudo - suponha que seu processo será eliminado no pior momento possível e escreva a cura para esse momento com antecedência.

O próximo artigo aborda uma parte mais interessante de Orkas: como esse agente aprende com seu próprio uso, transforma experiência em habilidades reutilizáveis ​​e lentamente se torna mais útil.