Ao fim desta lição você vai saber exatamente o que cada comando atômico faz — embed, ocr, triage, notes, context-pack, memory — e por que eles compõem para cima: vamos vetorizar o signal "fábrica de petições personalizadas", triá-lo, e guardar a nota da reunião da C.D Advocacia na memória — tudo offline, determinístico e a custo zero.
Cada brick desta lição é uma função run<Comando> deste arquivo, que escolhe um backend offline determinístico por padrão e devolve Result<T, Error>. É daqui que sai cada string de saída que você vai ver — nunca da memória paramétrica.
O Alembic não é um programa gigante e monolítico. Ele é feito de tijolos pequenos. Cada tijolo faz uma única coisa, faz bem, e pode ser encaixado em cima de outro. Esta lição apresenta seis desses tijolos — os mais atômicos do produto:
--online usa um servidor de OCR real.needs-triage…). Fecha em enums fixos.Os seis compartilham três promessas: fazem uma coisa só, rodam offline e de graça por padrão (resultado determinístico, sem rede), e compõem para cima — a saída de um vira a entrada de outro, e juntos eles formam comandos maiores como o funil e o AI Employee.
Pense como… uma caixa de peças LEGO. Cada peça é simples e burra sozinha; o valor está em encaixá-las. Onde a analogia quebra: peças LEGO são mudas — estes tijolos devolvem Result (sucesso-ou-erro como valor), então quando um encaixe não fecha, ele avisa em vez de explodir.
Result + fail-closed.embed, triage, context-pack e memory e saber o que cada campo significa."Atômico" aqui tem um sentido preciso: cada comando é uma função pura sobre um seam (uma porta injetável — um backend de embeddings, um adapter de modelo, um FsPort de arquivos) que devolve um Result<T, Error> de @alembic/contracts. O comando não conhece a rede; ele recebe o seam e o chama. Em modo offline, o seam é uma implementação determinística e em-processo (sem rede, sem SDK, sem Math.random(), sem Date.now() — a VM de planos proíbe os dois). Com --online, o mesmo comando recebe um seam real (cliproxyapi, SGLang) e — crucialmente — falha fechado se o backend não estiver configurado, sem nenhuma tentativa de rede silenciosa.
Essa uniformidade é o que torna o produto componível e testável: como todo brick tem a mesma forma (entrada validada → seam → Result validado por Zod), o harness pode injetar fakes nos testes ($0, hermético) e modelos reais em produção sem mudar uma linha do comando.
Olhe a caixa de ferramentas de cima. Seis tijolos, uma entrada e uma saída cada — e a anatomia comum embaixo: entrada → seam → Result.
embed como texto e sai como um vetor de 16 dimensões.Um tour rápido. Para cada tijolo: o que faz, a cara da saída real (verbatim do CLI), e onde ele se encaixa na história da C.D Advocacia.
embed — texto vira vetorVocê dá uma ou mais frases; ele devolve, para cada uma, uma lista de 16 números. Esse vetor é a "impressão digital" da frase. Vetores parecidos = textos parecidos (no modo --online real); no modo offline é uma impressão estável usada para plumbing e chaves de cache.
embed: 1 vector(s) of 16 dims (model text-embedding-3-small) preview: [0.2841, -0.5512, 0.1033, …]
ocr — imagem vira textoAponte para uma imagem (um print de uma petição, um documento escaneado da C.D). Offline, ele devolve um texto-placeholder estável — útil para testar a tubulação sem GPU. Com --online, usa um servidor de OCR real (e exige a variável ALEMBIC_OCR_BASEURL, senão falha fechado).
OCR(<caminho-da-imagem>). Não é reconhecimento de verdade — é uma impressão estável da entrada, suficiente para validar que o pipeline está conectado antes de você gastar com o backend real.OCR(<source>) — uma impressão da entrada, não reconhecimento real.triage — issue vira classificaçãoCole o texto de um chamado; ele devolve severidade, categoria, uma prioridade de 1 a 10, e labels tirados de um vocabulário fechado de cinco: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix.
triage: medium/question (priority 5/10) labels: needs-triage Offline deterministic triage placeholder (no model called)
triage é toda fechada: cada campo só aceita valores de uma lista pré-definida.notes — transcript vira notasDê o caminho de um arquivo de transcrição (a gravação da call de descoberta com a C.D). Ele extrai título, resumo, participantes, decisões e action items — cada ação com dono e prazo em ISO-8601, ou os sentinelas unassigned/unscheduled (nunca um "TBD" solto).
context-pack — arquivos viram um pacote em camadasDê uma lista de arquivos e um brief; ele monta um pacote de contexto em 8 camadas (L0 a L7) para alimentar um agente, com um orçamento de tokens por papel e por modelo, e um manifesto com hash de conteúdo. É o tijolo que prepara "tudo que o agente precisa saber" de forma estratificada.
context-pack: ctxp_… (role validator, model claude-opus-4-8-max) layers L0..L7 · budget 5501/100000
ctxp_… (hash) e um orçamento de tokens medido.memory — um fato vira uma linha gravadaGrava (ou lê) um registro numa de cinco sub-lojas de memória, cada uma um arquivo .jsonl só-append. É como o produto lembra de coisas entre execuções: o que aconteceu, o que aprendeu, o que decidiu.
memory episodic: appended mem-1 (agent demo) # depois: memory episodic: 1 record(s) … mem-1 [agent demo]
--online? embed (usa um modelo de embeddings, não de texto) e memory (puro IO de arquivo) — os outros quatro podem rotear um modelo de texto quando online.embed e ocr são pacotes próprios (@alembic/embeddings, @alembic/ocr), cada um com um backend offline e um online. triage e notes são templates tipados de @alembic/hermes — reescritas em idioma Alembic de agentes do catálogo 500-AI-Agents-Projects (a triage vem do agente 07-github-issue-triager; a notes do 10-meeting-notes-agent), só que com saídas fechadas em enums e validadas por Zod em vez do JSON livre do original. context-pack vive em @alembic/council. memory é o multi-store de @alembic/hermes — um ADAPT do subsistema de memória do Agent Swarm, trocando MongoDB/Redis por JSONL só-append sobre FsPort.
Aqui está o pulo do gato. Seis comandos diferentes, mas uma forma só. Entenda a forma uma vez e você entendeu os seis — e qualquer comando futuro que siga o molde.
Você roda alembic embed "oi" --online mas o gateway de modelos não está ligado nem o token configurado. O que acontece?
err(Error) com mensagem acionável — sem tentar a rede e sem gastar nada. A regra do produto é "fail-closed": diante da dúvida, pare e avise; nunca prossiga às cegas. (No offline, o mesmo comando simplesmente usa o backend determinístico e responde na hora.)Result. O erro é sempre um valor, nunca uma exceção.Backend determinístico, em-processo. Mesma entrada → mesma saída, sempre. Custo $0. Sem rede. Perfeito para testes herméticos, CI e para você experimentar sem configurar nada.
Troca o seam por um backend real (cliproxyapi para embed/triage/notes/context, SGLang para ocr). Faz preflight; se o backend não estiver lá, devolve err sem tentar a rede. Pode custar dinheiro — por isso é explícito.
err, $0, zero rede. Um flag sozinho nunca gasta — a confiança é estrutural.Em código, cada comando recebe o backend como um argumento ({ backend }) ou resolve um adapter via resolveTemplateAdapter(args.online, deps). A função de domínio (embed, triageIssue, buildLayeredContextPack) nunca instancia a rede; ela só conhece a interface do seam. Isso é injeção de dependência levada a sério: o teste passa um fake, a produção passa o real, e a função do meio não muda. É a mesma "cintura estreita" da Lição 0001 (Result<T,Error>) aplicada às fronteiras de IO.
O determinismo é uma invariante, não um detalhe: os backends offline são proibidos de chamar Date.now() e Math.random() (a VM de planos rejeita ambos). É por isso que o mesmo comando, com a mesma entrada, produz bytes idênticos — o que torna cache, replay e testes de regressão confiáveis.
Dois trechos reais. Primeiro, como o embed offline fabrica um vetor sem rede. Depois, a corrente fail-closed do triage.
// 16 slots; cada slot dobra TODOS os char codes (com a posição) // num acumulador de 32 bits, depois esmaga em [-1, 1). Puro. export const OFFLINE_DIMENSION = 16; const SLOT_SEED = 0x9e3779b1; // recíproco da razão áurea const foldSlot = (text, slot) => { const multiplier = (SLOT_SEED * (slot + 1)) >>> 0; let acc = (slot + 1) >>> 0; for (let i = 0; i < text.length; i += 1) { const mixed = Math.imul(acc ^ (text.charCodeAt(i) + i + 1), multiplier) >>> 0; acc = (mixed + ((acc << 5) >>> 0)) >>> 0; } return acc; // squash(acc) = (acc - 2^31) / 2^31 → [-1, 1) };
Sem rede, sem modelo, sem Math.random(). É um hash polinomial: cada uma das 16 posições mistura os mesmos caracteres com um multiplicador diferente, então frases distintas geram vetores distintos — de forma reprodutível.
export const triageIssue = async (rawInput, model) => { // 1) valida a entrada no limite — título vazio falha fechado const inputCheck = issueTriageInputSchema.safeParse(rawInput); if (!inputCheck.success) return err(toError(inputCheck.error)); const result = await model.adapter.run(runInput); if (!result.ok) return err(new Error(result.error.message)); // 2) extrai o JSON do texto do modelo (tolerante a prosa) const parsed = extractJsonObject(result.text); if (!parsed.ok) return err(parsed.error); // 3) valida a SAÍDA — enum errado / chave a mais falha fechado const validated = issueTriageOutputSchema.safeParse(parsed.value); if (!validated.success) return err(toError(validated.error)); return ok(validated.data); };
Quatro pontos de falha, cada um devolvendo err em vez de lançar: entrada inválida → adapter falhou → sem JSON → saída fora do schema. A função é pura sobre o adapter — ela não sabe se o modelo é real ou um fake offline.
O comentário do topo do arquivo explica em prosa por que isto NÃO é um embedding semântico — é uma impressão estável para plumbing e cache. A honestidade sobre o que offline é (e não é) é parte da disciplina.
No repo, os comandos são funções run<Comando> em apps/cli/src/commands.ts (busque por runEmbed, runTriage…). A lógica de domínio fica nos pacotes: packages/embeddings/src/embed.ts, packages/hermes/src/templates/issue-triage.ts. Rode qualquer um offline com, por exemplo, alembic embed "teste" — zero configuração, $0.
De todos os seis, o context-pack é o mais rico — vale uma imagem própria. Ele estratifica o contexto de um agente em 8 camadas, da mais estreita (uma linha) à mais larga (o manifesto completo com hash). Assim, sob pressão de orçamento, dá para descartar as camadas de baixo valor e ainda manter o pacote reproduzível pelo hash.
createdAt é injetado pelo CLI (resolvido do relógio no limite, formatado ISO), e o id do pacote (ctxp_…) e o do manifesto são hashes SHA-256 do conteúdo. Mesmos arquivos → mesmo pacote → mesmo id. É a mesma disciplina dos backends offline, aplicada a um artefato composto.alembic context-pack GOAL.md proposta.md --brief "petições personalizadas p/ C.D" --role validator.100000). A saída diz within budget ou OVER BUDGET.--role e --model definem o par; o default é validator / claude-opus-4-8-max.)O memory não é uma caixa única. São cinco sub-lojas, cada uma para um tipo de lembrança, cada uma um arquivo .jsonl só-append em <dataDir>/memory/<substore>.jsonl. "Só-append" quer dizer: nunca se edita uma linha — correções são linhas novas. É o mesmo modelo do events.jsonl do harness.
id/agent/at). O id vem de um contador monotônico (mem-1, mem-2…), nunca de randomUUID() — para ser determinístico em testes e replay.| Sub-loja | Guarda | Campos do corpo (além do envelope) |
|---|---|---|
| episodic | O que aconteceu | episode, context, salience (0..1, default 0.5) |
| semantic | Fatos aprendidos, citáveis | fato + proveniência |
| procedural | Como executar uma tarefa | passos / procedimento |
| decision | Decisões e seu racional | decisão + justificativa |
| transcript | Falas brutas de reuniões | trechos de transcrição |
update nem delete in-place. Um log só-append é a forma do FsPort — uma "correção" é um registro novo que vence o antigo na leitura newest-first. Isso preserva a história e mantém o replay determinístico.Aqui está a promessa de "compõem para cima" tornada tangível. Escolha os tijolos, ajuste a entrada, alterne offline/online, e veja a corrente rodar — a saída de um vira a entrada do próximo. Esta é uma simulação fiel das strings reais do CLI (nada chama a rede; é só o terminal aqui na página).
Clique nos tijolos para ligá-los na corrente (a ordem é fixa: cada um alimenta o próximo). Depois rode.
embed → o vetor → memory → o registro mem-1 gravado.embed produz um vetor de 16 dims; a corrente grava esse fato na memory episodic como um registro mem-1. Dois tijolos atômicos, uma composição útil — exatamente como o funil e o AI Employee são construídos por dentro.--online é sempre uma escolha explícita.Result<T, Error> — o erro é um valor, nunca uma exceção.embed offline é um hash polinomial de 16 dims — uma impressão, não significado semântico.triage fecha tudo em enums e 5 labels canônicos — sem JSON livre.context-pack estratifica em 8 camadas (L0→L7) com orçamento de tokens e manifesto com hash.memory são 5 sub-lojas só-append; correções são linhas novas, nunca edições.Date.now()/randomUUID().--online escolhe o modelo?", "o que é salience na memória?", ou "qual tijolo o funil chama primeiro?". Próxima lição (0004): subimos um andar — como o swarm e o harness orquestram um lead e seus workers, passando por todos os gates, para forjar o signal "fábrica de petições" numa venture.Passe pelos slides para fixar — depois teste-se nos três quizzes.
--online opta por um backend real e falha fechado se ele estiver ausente. Não há download nem dependência de rede.embed offline produz um vetor de 16 dimensões. O que esse vetor é?Math.random().)update/delete in-place?FsPort (espelha o events.jsonl do harness). Uma correção é um registro novo que vence na leitura newest-first — preserva a história e mantém o replay determinístico.