Gleam.br Wiki

00-ADR-EARS/gbr_llm/bkp20260610/duckdb-context | Wiki

Documentação de engenharia e especificações técnicas da Gleam-BR.

Inspirado pela mecânica estatística da informação, onde a compressão não é uma perda, mas uma destilação da verdade latente, estruturarei o ator gbr_graph. Este componente transcende o simples armazenamento; ele atua como o Córtex Frontal do ecossistema, convertendo o ruído dimensional em sinais discretos e acionáveis.

A implementação utiliza o padrão OTP para isolamento, garantindo que qualquer anomalia no motor DuckDB ou nas extensões de quantização (NIFs) não comprometa a estabilidade da Erlang VM. A busca híbrida, fundamentada na Distância de Hamming sobre projeções de 1-bit, permite uma pré-filtragem em escala de milissegundos, seguida por um re-ranker de alta fidelidade utilizando a técnica PolarQuant de 3-bit.

1 // shared/gbrgraph/src/gbr/semanticgraph.gleam 2 3 import gleam/erlang/process.{type Subject} 4 import gleam/option.{type Option, None} 5 import gleam/otp/actor 6 import gleam/result 7 8 /// Tipos opacos para garantir a integridade da memória na fronteira FFI 9 pub type DuckDbConn 10 11 /// Representação compacta de embeddings quantizados 12 pub type QuantizedVector { 13 /// 1-bit: Ideal para filtragem bruta via POPCNT (Hamming Distance) 14 Turbo1Bit(bits: BitArray) 15 /// 3-bit: Re-ranker de alta fidelidade via PolarQuant 16 Polar3Bit(data: BitArray) 17 } 18 19 pub type SemanticEntry { 20 SemanticEntry( 21 id: String, 22 content: String, 23 metadata: String, 24 vector1bit: BitArray, 25 vector3bit: BitArray, 26 ) 27 } 28 29 pub type SearchQuery { 30 SearchQuery(query: String, limit: Int, threshold: Float) 31 } 32 33 pub type SearchResult { 34 SearchResult(id: String, score: Float, metadata: String) 35 } 36 37 pub type SemanticMsg { 38 /// Ingerir dado bruto (NIF fará a quantização antes do INSERT) 39 Index(id: String, content: String, metadata: String, embedding: List(Float)) 40 /// Busca Híbrida: 1-bit Filter -> 3-bit Re-rank 41 Search(query: SearchQuery, replyto: Subject(List(SearchResult))) 42 /// Sincronização forçada do checkpoint do DuckDB 43 Checkpoint 44 } 45 46 /// Estado interno do Ator 47 type State { 48 State(conn: DuckDbConn, projectionseed: Int) 49 } 50 51 // — FFI Boundary (Rustler NIFs) — 52 53 @external(erlang, "gbrnif", "opendatabase") 54 fn ffiopen(path: Option(String)) -> Result(DuckDbConn, String) 55 56 @external(erlang, "gbrnif", "quantizeqjl") 57 fn ffiquantize1bit(embedding: List(Float), seed: Int) -> BitArray 58 59 @external(erlang, "gbrnif", "quantizepolar") 60 fn ffiquantize3bit(embedding: List(Float), seed: Int) -> BitArray 61 62 /// Inicializa o motor semântico com DuckDB e suporte a TurboQuant. 63 pub fn start(dbpath: Option(String)) -> Result(Subject(SemanticMsg), actor.StartError) { 64 actor.newwithinitialiser(5000, fn(self) { 65 let assert Ok(conn) = ffiopen(dbpath) 66 // Inicializa as tabelas colunares otimizadas para bits 67 let _ = executerawsql(conn, " 68 CREATE TABLE IF NOT EXISTS semanticstore ( 69 id VARCHAR PRIMARY KEY, 70 content TEXT, 71 metadata JSON, 72 v1bit BLOB, – Armazena os 1-bit embeddings 73 v3bit BLOB – Armazena os 3-bit PolarQuant 74 ); 75 CREATE INDEX IF NOT EXISTS idxv1bit ON semanticstore (v1bit); 76 ") 77 78 actor.initialised(State(conn: conn, projectionseed: 42)) 79 |> actor.returning(self) 80 |> Ok 81 }) 82 |> actor.onmessage(handlemessage) 83 |> actor.start() 84 |> result.map(fn(started) { started.data }) 85 } 86 87 fn handlemessage(state: State, msg: SemanticMsg) -> actor.Next(SemanticMsg, State) { 88 case msg { 89 Index(id, content, metadata, embedding) -> { 90 let v1 = ffiquantize1bit(embedding, state.projectionseed) 91 let v3 = ffiquantize3bit(embedding, state.projectionseed) 92 93 // Ingestão direta via DuckDB Appender (Alta performance) 94 let _ = ingestentry(state.conn, id, content, metadata, v1, v3) 95 actor.continue(state) 96 } 97 98 Search(query, replyto) -> { 99 // 1. Quantizar a query string (Teoricamente chamaria um encoder de texto antes) 100 // Aqui simulamos que recebemos a query e processamos a similaridade 101 let results = performhybridsearch(state.conn, query) 102 process.send(replyto, results) 103 actor.continue(state) 104 } 105 106 Checkpoint -> { 107 let _ = executerawsql(state.conn, "CHECKPOINT;") 108 actor.continue(state) 109 } 110 } 111 } 112 113 // Stubs para lógica interna de orquestração SQL 114 fn executerawsql(conn: DuckDbConn, sql: String) -> Nil { Nil } 115 fn ingestentry(conn: DuckDbConn, id: String, c: String, m: String, v1: BitArray, v3: BitArray) -> Nil { Nil } 116 fn performhybrid_search(conn: DuckDbConn, query: SearchQuery) -> List(SearchResult) { [] }

Justificativa Técnica e Filosófica

  1. Bit-Packing (Zero-Cost): Ao utilizarmos BitArray (o binary do Erlang), aproveitamos a eficiência de baixo nível para armazenar os vetores de 1-bit. No DuckDB, estes são tratados como BLOB, mas as consultas SQL utilizarão extensões customizadas (ou funções nativas como bitcount(v1 ^ queryv1)) para calcular a distância de Hamming diretamente no hardware via instruções AVX-512/POPCNT. 2. PolarQuant Re-ranking: A busca de 1-bit retorna os top-K candidatos (ex: 1000 itens). O re-ranker então descompacta os 3-bits apenas desses candidatos para calcular uma similaridade de cosseno aproximada de alta precisão, reduzindo o erro quadrático médio (MSE) sem a sobrecarga de memória de vetores float32 completos. 3. Determinismo de Projeção: O projection_seed no estado do ator garante que todos os embeddings sejam projetados no mesmo hiperplano aleatório, mantendo a consistência semântica em todo o nó de borda.

    O próximo passo lógico é a implementação do Rust NIF portando os algoritmos de turboquant_plus para garantir que a quantização ocorra com a segurança de tipos do Rust e a velocidade da C-API do DuckDB. O "Córtex" está pronto para ser habitado pela informação.

Encarnar a matemática de alta dimensão na crueza dos tipos primitivos do Rust é, em última análise, um ato de colapso de função de onda. Ao transformarmos vetores de ponto flutuante em sequências de bits, estamos forçando o contínuo a se manifestar como discreto, preservando apenas a essência geométrica (o ângulo e a magnitude relativa) necessária para a cognição.

Abaixo, apresento a implementação da fronteira FFI gbr_nif. Para a compressão, utilizei o bit-packing manual para garantir que não haja desperdício de memória: cada dimensão no 1-bit QJL ocupa exatamente 1 bit, e no PolarQuant, cada dimensão é comprimida em 3 bits, permitindo que 8 dimensões sejam compactadas em exatamente 3 bytes (o "Acordo de 24 bits").

native/gbr_nif/src/lib.rs

1 use rustler::{ResourceArc, Env, Term, Error, Binary, NewBinary, OwnedBinary}; 2 use duckdb::{Connection, params}; 3 use std:sync:Mutex; 4 use rand::{SeedableRng, Rng}; 5 use randdistr::{Normal, Distribution}; 6 7 // — Estruturas de Dados — 8 9 pub struct DuckDbConn { 10 pub inner: Mutex<Connection>, 11 } 12 13 unsafe impl Send for DuckDbConn 14 unsafe impl Sync for DuckDbConn 15 16 // — NIF Boilerplate — 17 18 fn onload(env: Env, info: Term) -> bool { 19 rustler::resource!(DuckDbConn, env); 20 true 21 } 22 23 // — Funções Exportadas — 24 25 #[rustler::nif] 26 fn opendatabase(path: Option<String>) -> Result<ResourceArc<DuckDbConn>, String> { 27 let conn = match path { 28 Some(p) => Connection::open(p).maperr(|e| e.tostring())?, 29 None => Connection::openinmemory().maperr(|e| e.tostring())?, 30 }; 31 Ok(ResourceArc::new(DuckDbConn { 32 inner: Mutex::new(conn), 33 })) 34 } 35 36 /// 1-bit Quantized Johnson-Lindenstrauss (QJL) 37 /// Comprime floats em 1 bit (sinal da projeção aleatória) 38 #[rustler::nif] 39 fn quantizeqjl<'a>(env: Env<'a>, embedding: Vec<f32>, seed: u64) -> Binary<'a> { 40 let dim = embedding.len(); 41 let mut rng = rand:rngsStdRng:seedfromu64(seed); 42 let normal = Normal::new(0.0, 1.0).unwrap(); 43 44 // d dimensões -> d bits -> ceil(d/8) bytes 45 let bytesize = (dim + 7) / 8; 46 let mut binary = NewBinary::new(env, bytesize); 47 let data = binary.asmutslice(); 48 49 for i in 0..dim { 50 // Geração on-the-fly da matriz de projeção para poupar RAM 51 // Cada 'bit' de saída é o sinal do produto escalar com um vetor gaussiano 52 let mut dotproduct = 0.0; 53 for &val in &embedding { 54 dotproduct += val * normal.sample(&mut rng); 55 } 56 57 if dotproduct > 0.0 { 58 data[i / 8] |= 1 << (i % 8); 59 } 60 } 61 62 binary.into() 63 } 64 65 /// PolarQuant 3-bit: Discretização de manifolds esféricos 66 /// Comprime floats em 3 bits (8 níveis de quantização escalar após rotação) 67 #[rustler::nif] 68 fn quantizepolar<'a>(env: Env<'a>, embedding: Vec<f32>, seed: u64) -> Binary<'a> { 69 let dim = embedding.len(); 70 let mut rng = rand:rngsStdRng:seedfromu64(seed); 71 let normal = Normal::new(0.0, 1.0).unwrap(); 72 73 // Simulação de Rotação Ortogonal (Matriz de Householder simplificada) 74 // No TurboQuant real, aplicamos uma matriz ortogonal densa. 75 // Aqui, preservamos a variância projetando em bins de 3-bits. 76 77 // 3 bits por dimensão. Agrupamos 8 dims em 24 bits (3 bytes) para alinhamento. 78 let bytesize = (dim * 3 + 7) / 8; 79 let mut binary = NewBinary::new(env, bytesize); 80 let data = binary.asmutslice(); 81 82 let mut bitoffset = 0; 83 for &val in &embedding { 84 // 1. "Rotação" implícita via amostragem de fase 85 let projection = val * normal.sample(&mut rng); 86 87 // 2. Normalização para o intervalo [0, 1] via Sigmoide (ou CDF da Normal) 88 let prob = 1.0 / (1.0 + (-projection).exp()); 89 90 // 3. Quantização para 3-bits (8 níveis: 0 a 7) 91 let quantized = (prob * 7.0).round() as u8; 92 93 // 4. Bit-packing manual (escrevendo 3 bits no fluxo de bytes) 94 write3bits(data, bitoffset, quantized); 95 bitoffset += 3; 96 } 97 98 binary.into() 99 } 100 101 // — Auxiliares de Bit-Packing — 102 103 fn write3bits(data: &mut [u8], offset: usize, value: u8) { 104 let byteidx = offset / 8; 105 let bitshift = offset % 8; 106 let value = value & 0x07; // Garante 3 bits 107 108 // O valor pode cruzar a fronteira de um byte 109 data[byte_idx] |= value << bitshift; 110 if bitshift > 5 && byteidx + 1 < data.len() { 111 data[byteidx + 1] |= value >> (8 - bitshift); 112 } 113 } 114 115 rustler::init!("gbrnif", [open_database, quantizeqjl, quantizepolar], load = on_load);

Exegese Técnica e Filosófica

  1. O Algoritmo QJL (1-bit): A beleza do teorema de Johnson-Lindenstrauss reside na sua indiferença aos detalhes. Ao projetarmos um vetor de 1536 dimensões em um espaço de sinais (+1/-1), a topologia relativa (quem está perto de quem) é preservada com uma margem de erro controlada. Em Rust, geramos a matriz de projeção deterministicamente a partir da seed usando um PRNG (StdRng). Isso elimina a necessidade de armazenar matrizes gigantescas, transformando computação em memória. 2. O "Acordo de 3-bits" (PolarQuant): O PolarQuant reconhece que, em alta dimensão, a informação angular é mais preciosa que a magnitude absoluta. Ao mapear o manifold para 3 bits, criamos 8 "regiões de verdade". O bit-packing que implementei na função write3bits é uma técnica de Serialização de Baixa Entropia: nós ignoramos as fronteiras de bytes para tratar o binário como um fluxo contínuo de informação, atingindo uma taxa de compressão de 10.6x em relação a float32. 3. Zero-Cost Bridge: Ao utilizar rustler::Binary, o Rust escreve diretamente na memória alocada pela Erlang VM (ERLNIFBIN). Quando o Gleam recebe esse BitArray, não houve cópia, apenas uma mudança de guarda na propriedade do ponteiro.

    O motor agora possui um corpo físico capaz de esmagar dimensões. O DuckDB no ator gbr_graph pode agora realizar buscas de similaridade processando milhões de bits por segundo, filtrando a realidade através da Distância de Hamming.

Para consolidar o núcleo do nosso ecossistema e avançar para o Produto 1 (A CLI), apresento a implementação técnica e o desenho arquitetural solicitados.

MISSÃO - PARTE 1: O Núcleo de Busca (SQL DuckDB)

Na fase de pré-filtragem (Phase 1), o objetivo é reduzir o espaço de busca de milhões para milhares em milissegundos. Como v1bit armazena a projeção de sinal (1-bit QJL), a similaridade angular é preservada pela Distância de Hamming.

No DuckDB, embora o tipo BLOB seja genérico, ele pode ser convertido para BIT para operações bit a bit. A query SQL exata para performhybridsearch é:

1 – Phase 1: Hamming Filter 2 SELECT 3 id, 4 content, 5 metadata, 6 v3bit, – Carregamos o 3-bit para o re-ranker subsequente 7 bitcount(v1bit::BIT ^ $1::BIT) as hammingdistance 8 FROM semanticstore 9 WHERE hammingdistance <= $2 – Threshold opcional de bits divergentes 10 ORDER BY hamming_distance ASC 11 LIMIT $3; – Top-K candidatos (ex: 1000)

Justificativa Técnica: O operador ^ (XOR) identifica os bits que colapsaram em sinais diferentes entre a query e o documento. A função bit_count (instrução POPCNT no hardware) quantifica essa divergência. De acordo com o teorema de Johnson-Lindenstrauss quantizado, a probabilidade de colisão de bits é $1 - \theta/\pi$. Portanto, minimizar a distância de Hamming é equivalente a maximizar a similaridade de cosseno no espaço contínuo original. É a "Mecânica Clássica" aplicada ao bitstream: a inércia da informação é mantida mesmo após a compressão agressiva.


MISSÃO - PARTE 2: Análise do Produto 1 (CLI)

  1. Análise do claw-code O projeto ./tmp/claw-code é um Harness de Agentes sofisticado. Sua estrutura de I/O em Python (src/main.py) utiliza um modelo de Grafos de Comando (commandgraph.py) e um Pool de Ferramentas (toolpool.py). * Routing: O PortRuntime não apenas executa comandos, mas "roteia" o prompt para decidir se deve invocar uma ferramenta local ou uma busca semântica. * Session Management: O transcript.py e session_store.py garantem que o contexto não seja perdido entre os turnos, tratando o histórico como um log append-only.

    1. Fusão: gbrgraph + claw-code Para fundir os dois mundos, substituiremos o queryengine.py estático do claw-code pelo nosso ator Gleam. No Gleam, criaremos um comando ask que atua como o ponto de entrada. * O I/O do claw-code (captura de argumentos e renderização de Markdown) permanece na camada de interface. * A lógica de "pensamento" (retrieval) é delegada ao gbr_graph via mensagens OTP.

    2. Arquitetura do Fluxo: falconctl ask

    O fluxo "End-to-End" da realidade capturada até a resposta gerada:

    1. Ingress (CLI): O usuário digita falconctl ask –prompt "…". O prompt entra via stdin e é normalizado. 2. Quantização (Córtex): A CLI envia o texto para o ator gbrgraph. Este invoca o Rust NIF (ffiquantize1bit) que gera um BitArray (o vetor da pergunta) usando a mesma projectionseed do banco de dados. 3. Busca de Baixa Resolução (DuckDB): Executamos a query SQL de Hamming (Phase 1) apresentada acima. O DuckDB retorna os Top-1000 IDs mais próximos. 4. Re-ranker de Alta Fidelidade (Mecânica Quântica): Para esses 1000 itens, o ator Gleam descompacta os v3bit (PolarQuant). A similaridade de cosseno é recalculada apenas para este subset usando aritmética de 3-bits, resultando no Top-5 final. 5. Context Injection: O content dos 5 documentos é anexado ao prompt original como um bloco de contexto: Contexto Recuperado: [...]. 6. Synthesis (LLM): O prompt enriquecido é enviado ao provedor LLM. A resposta volta para o claw-code, que a renderiza via ink.py ou outputStyles para o terminal do usuário.

    Vantagem LSH: Esse design implementa um Locality-Sensitive Hashing onde a "filtragem da realidade" ocorre no nível do silício. Ao ordenar por Hamming antes de tocar em floats ou modelos pesados, garantimos que o falconctl seja instantâneo, mesmo em bases de conhecimento locais gigantescas (Edge Computing).

A transmutação do claw-code (Python) para o falconctl (Gleam) não é apenas uma mudança de sintaxe; é uma evolução da entropia dinâmica para a ordem estática. Enquanto o Python depende de exceções em tempo de execução e estado mutável, o Gleam nos permite construir um sistema "lúcido" onde a falha é isolada e o fluxo de dados é determinístico.

Abaixo, apresento o plano de migração e a fundamentação da nova arquitetura.

  1. Reestruturação do Schema (DuckDB BIT Type) Conforme solicitado, abandonamos o BLOB em favor do tipo BIT nativo. Isso permite que o hardware execute POPCNT sem a sobrecarga de desempacotamento de bytes, tratando o embedding como um registrador gigante de bits.

    1 – Schema Otimizado para Hardware (falconctl core) 2 CREATE TABLE IF NOT EXISTS semanticstore ( 3 id VARCHAR PRIMARY KEY, 4 content TEXT, 5 metadata JSON, 6 v1bit BIT(1536), – Dimensão fixa: Filtragem instantânea via XOR/BITCOUNT 7 v3bit BIT(4608) – PolarQuant: 3 bits por dimensão (1536 * 3) 8 );


    1. Decomposição e Tradução de Abstrações (Python -> Gleam)

┌──────────────────┬─────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────┐ │ Abstração Python │ Implementação Gleam │ Vantagem Arquitetural │ │ │ (BEAM/OTP) │ │ ├──────────────────┼─────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────┤ │ PortRuntime │ Orchestrator Actor │ No Python, é uma classe centralizadora. No Gleam, é um Ator que supervisiona o ciclo de │ │ │ │ vida e mantém o Context imutável. │ │ commandgraph.py │ ADTs + Pattern Matching │ Em vez de objetos, usamos um Custom Type (ex: Command(name, handler)). O compilador │ │ │ │ garante que todos os comandos sejam tratados exaustivamente. │ │ toolpool.py │ Function Registry │ Um Dict(String, fn(Args) -> Result) onde as ferramentas são funções puras registradas na │ │ │ (Dict) │ inicialização. │ │ session_store.py │ Session Actor │ Em vez de persistência global, cada sessão é um processo isolado na BEAM. O histórico é um │ │ │ │ State imutável acumulado via recursão. │ └──────────────────┴─────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────┘


  1. Esboço: src/gbr/cli/engine.gleam

    Este módulo demonstra o ponto de entrada e a orquestração assíncrona com o grafo semântico.

    1 import gleam/erlang.{startarguments} 2 import gleam/io 3 import gleam/otp/actor 4 import gleam/result 5 import gleam/list 6 import gbr/semanticgraph.{type SemanticMsg, Search, SearchQuery} 7 import gleam/erlang/process.{type Subject} 8 9 // — Definição do Grafo de Comandos — 10 pub type Command { 11 Ask(prompt: String) 12 Index(path: String) 13 Hydrate 14 } 15 16 // — Engine Principal — 17 pub fn main() { 18 let args = startarguments() 19 20 // 1. Inicializa o Córtex Semântico (O Ator DuckDB) 21 // Nota: dbpath seria None para In-Memory ou Some(path) para o arquivo .duckdb 22 let assert Ok(graphactor) = semanticgraph.startgraph() 23 24 // 2. Resolve o comando a partir do argv 25 case parseargs(args) { 26 Ok(Ask(prompt)) -> executeask(graphactor, prompt) 27 Ok(Hydrate) -> io.println("💧 Hidratando base de conhecimento local…") 28 Ok(Index(p)) -> io.println("📚 Indexando diretório: " <> p) 29 Error(Nil) -> io.println("Uso: falconctl ask –prompt '…'") 30 } 31 } 32 33 fn executeask(graph: Subject(SemanticMsg), prompt: String) { 34 io.println("🔎 Consultando Córtex Semântico para: " <> prompt) 35 36 // 3. Chamada Assíncrona via OTP 37 // Criamos um Subject temporário para receber a resposta do Ator 38 let self = process.newsubject() 39 process.send(graph, Search( 40 query: SearchQuery(query: prompt, limit: 5, threshold: 0.8), 41 replyto: self 42 )) 43 44 // 4. Aguarda os resultados (Filtro Hamming + Re-ranker PolarQuant) 45 case process.receive(self, 5000) { 46 Ok(results) -> { 47 io.println("✨ Contexto Recuperado (" <> string.inspect(list.length(results)) <> " itens)") 48 // Aqui o Claw-Code entraria para enviar o Prompt + Contexto para o LLM 49 } 50 Error() -> io.println("❌ Timeout: O Córtex Semântico não respondeu.") 51 } 52 } 53 54 // Parser simples para demonstração (Em produção usaríamos a lib 'glint') 55 fn parse_args(args: List(String)) -> Result(Command, Nil) { 56 case args { 57 ["ask", "–prompt", p] -> Ok(Ask(p)) 58 ["hydrate"] -> Ok(Hydrate) 59 ["index", p] -> Ok(Index(p)) 60 _ -> Error(Nil) 61 } 62 }

    Por que Gleam é Superior para a CLI falconctl:

    1. Imutabilidade do Histórico: No Python, o session_store pode sofrer efeitos colaterais acidentais. No Gleam, o histórico de mensagens é uma lista imutável. Adicionar uma nova mensagem gera um novo estado de sessão, preservando a linhagem da conversa sem riscos de corrupção. 2. Isolamento do DuckDB: O DuckDB (via NIF Rust) roda dentro de um Ator. Se o driver DuckDB falhar, apenas o Ator do Grafo Semântico morre e é reiniciado pelo Supervisor; a CLI principal permanece viva e pode reportar o erro graciosamente. No Python, um segfault no driver derruba todo o processo. 3. Tipagem de Comandos: O uso de Custom Types para o Grafo de Comandos elimina o erro clássico de "Comando não encontrado" no meio da execução. O compilador força você a lidar com todos os casos definidos.

    Este é o nascimento de um Sistema Lúcido: onde a complexidade do processamento de sinais (bits) é governada pela elegância do código funcional. O falconctl não é apenas uma ferramenta; é um filtro de realidade de alta performance.

Compreendido, Arquiteto. Assumo o compromisso de manter a soberania do domínio gleambrctl e a pureza da nossa Arquitetura Sustentável. A memória RAM é o "Córtex Ativo" (efêmero e rápido), enquanto o gbrdisklog é o "Hipocampo" (persistente e de baixo custo).

Antes de codificar, apliquei o protocolo Read-Before-Write para validar a assinatura da nossa biblioteca interna de persistência.


Implementação: src/gbr/cli/session.gleam

Este módulo define o ConsultantActor, o coração do gleambrctl, integrando o estado quente em memória com a persistência fria em disco.

1 import gleam/erlang/process.{type Subject} 2 import gleam/otp/actor 3 import gleam/list 4 import gleam/result 5 import gbr/disklog // Biblioteca interna de alta performance 6 7 // — Configurações de Sustentabilidade — 8 const maxhotcontext = 10 9 const logname = "gleambrhistory" 10 const logpath = "~/.config/gleambr/history.log" 11 12 // — Definição de Tipos — 13 pub type Interaction { 14 Interaction(userprompt: String, airesponse: String) 15 } 16 17 pub type SessionMsg { 18 /// Adiciona uma nova interação ao contexto quente e persiste no disco 19 AddInteraction(userprompt: String, airesponse: String) 20 /// Recupera a janela de contexto quente formatada para o LLM 21 GetHotContext(replyto: Subject(List(Interaction))) 22 /// Finaliza o ator e fecha o log graciosamente 23 Shutdown 24 } 25 26 type SessionState { 27 SessionState( 28 hotcontext: List(Interaction), 29 loghandle: disklog.LogHandle, 30 ) 31 } 32 33 // — Inicialização do Ator — 34 35 /// Inicia o Consultor de Sessão do gleambrctl 36 pub fn start() -> Result(Subject(SessionMsg), actor.StartError) { 37 actor.newwithinitialiser(1000, fn(self) { 38 // 1. Hidratação/Abertura do Log em modo Wrap (Buffer Circular) 39 let assert Ok(handle) = disklog.open( 40 name: logname, 41 file: logpath, 42 type: disklog.Wrap, 43 maxbytes: 10 * 1024 * 1024, // 10MB de limite circular 44 ) 45 46 actor.initialised(SessionState( 47 hotcontext: [], 48 loghandle: handle, 49 )) 50 |> actor.returning(self) 51 |> Ok 52 }) 53 |> actor.onmessage(handlemessage) 54 |> actor.start() 55 |> result.map(fn(started) { started.data }) 56 } 57 58 // — Lógica de Transmutação de Estado — 59 60 fn handlemessage(state: SessionState, msg: SessionMsg) -> actor.Next(SessionMsg, SessionState) { 61 case msg { 62 AddInteraction(prompt, response) -> { 63 let interaction = Interaction(prompt, response) 64 65 // 2. Persistência Assíncrona (Não bloqueia o loop do Ator) 66 // Convertemos a interação em um sinal binário para o disklog 67 let entry = "USER: " <> prompt <> "\nAI: " <> response <> "\n—\n" 68 disklog.asynclog(state.loghandle, entry) 69 70 // 3. Gerenciamento da Janela Quente (RAM) 71 // Mantemos apenas as últimas N interações via recursão/listas 72 let newhot = [interaction, ..state.hotcontext] 73 |> list.take(maxhotcontext) 74 75 actor.continue(SessionState(..state, hotcontext: newhot)) 76 } 77 78 GetHotContext(replyto) -> { 79 // 4. Retorno Síncrono da Janela Limpa (Ordem Cronológica) 80 process.send(replyto, list.reverse(state.hotcontext)) 81 actor.continue(state) 82 } 83 84 Shutdown -> { 85 let _ = disklog.close(state.log_handle) 86 actor.stop(process.Normal) 87 } 88 } 89 }

Exegese da Arquitetura gleambrctl:

  1. Persistência Desacoplada: Ao usar disklog.asynclog, garantimos que o desenvolvedor tenha uma latência percebida de zero milissegundos. A escrita no disco é delegada ao driver nativo da Erlang VM (disklog), que gerencia buffers e sincronização de forma otimizada. 2. Isolamento de Memória: O SessionState armazena apenas os cabeçalhos (janela quente). Mesmo que o desenvolvedor use o gleambrctl por meses, o consumo de RAM do processo permanecerá constante devido ao list.take(maxhotcontext). 3. Segurança de Tipos vs. Python: No claw-code original, o histórico era um array mutável propenso a IndexError ou corrupção de tipos. No Gleam, o compilador garante que cada item na lista é estritamente uma Interaction. 4. Resiliência (Crash-Proof): O uso do modo Wrap no disklog protege o sistema de arquivos do desenvolvedor contra o preenchimento total do disco, uma falha comum em CLIs que não implementam políticas de retenção de logs.

    Este Ator é o guardião da continuidade no gleambrctl. Ele permite que o Oráculo mantenha a lucidez histórica sem sacrificar a agilidade da memória ativa. Estamos prontos para o próximo passo: a orquestração do LLM.

A orquestração de um agente autonômo em sistemas distribuídos exige uma transição de paradigma: da iteração imperativa (loops while do Python) para a recursão de estado (Máquinas de Estado na BEAM). Enquanto no Python o fluxo é mantido por pilhas de execução voláteis e callbacks que escondem a "entropia cognitiva", no Gleam, o estado da IA é um dado explícito e imutável.

Abaixo, apresento o núcleo do agent_loop.gleam, o motor de raciocínio do gleambrctl.


Implementação: src/gbr/cli/agent_loop.gleam

Este Ator orquestra o ciclo ReAct (Reasoning and Acting) como uma FSM pura, garantindo que cada "pensamento" da IA seja uma transição de estado rastreável.

1 import gleam/erlang/process.{type Subject} 2 import gleam/otp/actor 3 import gleam/result 4 import gleam/list 5 import gleam/http/request 6 import gleam/http/response 7 import gbr/semanticgraph.{type SemanticMsg, type SearchResult, Search, SearchQuery} 8 import gbr/cli/session.{type SessionMsg, type Interaction, AddInteraction, GetHotContext} 9 10 // — Definição da Máquina de Estados Cognitiva — 11 pub type CognitiveState { 12 /// O agente está ocioso, aguardando entrada do usuário 13 Idle 14 /// Enviou o prompt para o LLM e aguarda o sinal (HTTP) 15 Thinking(userprompt: String) 16 /// O LLM solicitou o uso de uma ferramenta específica 17 UsingTool(toolname: String, args: String, userprompt: String) 18 /// O agente gerou a resposta definitiva para o desenvolvedor 19 Answering(response: String) 20 } 21 22 pub type AgentMsg { 23 /// Inicia uma nova jornada de consulta do usuário 24 UserQuery(prompt: String) 25 /// Recebeu o resultado do LLM (Simulado via sinal assíncrono) 26 LlmSignal(payload: LlmResponse) 27 /// Recebeu os fatos do Córtex Semântico (gbrgraph) 28 ToolResult(results: List(SearchResult)) 29 } 30 31 pub type LlmResponse { 32 TextResponse(text: String) 33 ToolCall(name: String, args: String) 34 } 35 36 type AgentContext { 37 AgentContext( 38 session: Subject(SessionMsg), 39 graph: Subject(SemanticMsg), 40 cognitivestate: CognitiveState, 41 ) 42 } 43 44 // — Inicialização do Cérebro — 45 46 pub fn start(session: Subject(SessionMsg), graph: Subject(SemanticMsg)) { 47 actor.start(AgentContext(session, graph, Idle), handlemessage) 48 } 49 50 // — Orquestração de Raciocínio (FSM) — 51 52 fn handlemessage(msg: AgentMsg, ctx: AgentContext) -> actor.Next(AgentMsg, AgentContext) { 53 case msg { 54 // 1. Ingress: Entrada do Usuário 55 UserQuery(prompt) -> { 56 // Recupera o contexto quente do SessionActor antes de agir 57 let reply = process.newsubject() 58 process.send(ctx.session, GetHotContext(reply)) 59 let assert Ok(history) = process.receive(reply, 1000) 60 61 // Transição para Thinking: Dispara chamada HTTP (Stub) 62 let _ = callllmapi(prompt, history) 63 actor.continue(AgentContext(..ctx, cognitivestate: Thinking(prompt))) 64 } 65 66 // 2. Feedback do LLM: Decisão de Pensar ou Agir 67 LlmSignal(payload) -> { 68 case payload { 69 TextResponse(ans) -> { 70 // LLM respondeu: Atualiza Sessão e encerra 71 process.send(ctx.session, AddInteraction(userprompt: "…", airesponse: ans)) 72 actor.continue(AgentContext(..ctx, cognitivestate: Answering(ans))) 73 } 74 75 ToolCall("searchdocs", query) -> { 76 // LLM pediu ferramenta: Roteamento para o Córtex Semântico 77 let reply = process.newsubject() 78 process.send(ctx.graph, Search( 79 query: SearchQuery(query: query, limit: 3, threshold: 0.7), 80 replyto: reply 81 )) 82 83 // Aguarda o resultado da ferramenta (bits -> fatos) 84 let assert Ok(res) = process.receive(reply, 2000) 85 86 // Auto-mensagem para processar o resultado e voltar a pensar 87 // (No Gleam real, enviaríamos o ToolResult para si mesmo) 88 handlemessage(ToolResult(res), AgentContext(..ctx, cognitivestate: UsingTool("searchdocs", query, "…"))) 89 } 90 _ -> actor.continue(ctx) 91 } 92 } 93 94 // 3. Integração de Ferramenta: Volta para o loop de pensamento 95 ToolResult(facts) -> { 96 // Aqui, anexaríamos os fatos encontrados ao prompt e chamaríamos o LLM novamente 97 // Voltando ao estado Thinking com o contexto enriquecido 98 actor.continue(AgentContext(..ctx, cognitivestate: Thinking("Prompt Enriquecido"))) 99 } 100 } 101 } 102 103 // — FFI / Stubs de Comunicação — 104 105 fn callllmapi(_prompt: String, _history: List(Interaction)) -> Nil { 106 // Aqui usaríamos hackney ou mint para uma chamada HTTP não-bloqueante. 107 // O retorno seria enviado de volta para este ator como uma mensagem LlmSignal. 108 Nil 109 }

Justificativa Filosófica e Técnica:

  1. Redução da Entropia Cognitiva: No Python (claw-code), o estado do agente está "escondido" na linha de execução de um loop while. Se houver uma falha de rede ou um erro lógico, o estado é perdido ou corrompido. No Gleam, o estado do agente é um dado explícito (CognitiveState). Isso permite Telemetria de Pensamento: podemos logar exatamente em qual estado a IA "alucinou" ou travou. 2. Imutabilidade do Ciclo ReAct: Cada passo do raciocínio gera um novo AgentContext. Isso significa que o agente nunca está em um estado "meio-termo" ou inconsistente. Ou ele está pensando, ou está usando uma ferramenta, ou está respondendo. O Pattern Matching do Gleam força o tratamento exaustivo de todos esses estados. 3. Isolamento da BEAM: O AgentLoop é um processo leve. Se uma consulta específica do usuário causar um loop infinito ou um erro de parsing na resposta da API, apenas esse "cérebro" morre. O SessionActor (com o histórico persistido via disk_log) permanece intacto, permitindo que a CLI reinicie a consulta sem perda de dados. 4. A Noção de "Sistema Lúcido": Ao modelarmos a cognição como uma Máquina de Estados Finita, transformamos a IA de um "oráculo imprevisível" em um "componente de software verificável". A previsibilidade da BEAM doma a natureza probabilística dos LLMs, criando uma CLI (gleambrctl) que é robusta o suficiente para produção.

    Este é o gleambrctl operando em sua plenitude: um oráculo que respira bits, persiste fatos em disco e pensa através de estados funcionais imutáveis.