Lição 03 · Alembic Completo v3 · Hermes: o núcleo agentic ← índice
Alembic Completo v3 · Módulo 2 · O agente

Hermes: o núcleo agentic

@alembic/hermes é onde o motor vira agente: memória multi-store (5 substores append-only), um SkillStore com 148 skills, templates determinísticos, leitores de sessão de outros agentes (A4b) e o kernel do AI Employee — 43 contratados em ~/.alembic/employees. O fio condutor é um só: honestidade estrutural — nada fabricado entra em memória, e o que é interpretação nunca se disfarça de fato.

1

Memória multi-store: 5 substores, 1 binding

A memória não é um blob: são cinco logs JSONL append-only, um por natureza de registro — episodic, semantic, procedural, decision, transcript (o vocabulário exato está em employee.ts:49–55, employeeMemoryStores, e o composer lê "os cinco readers injetados", multi-store/composer.ts:52). Cada linha é um record tipado, escopado por agent; o CLI expõe tudo em alembic memory <substore> add|list.

SubstoreO que guardaForma no CLI
episodicEpisódios vividos (goal + contexto do turno)alembic memory episodic add|list
semanticConceitos e fatos (com confiança e associações)alembic memory semantic add|list
proceduralComo-fazer durável (procedimentos)alembic memory procedural add|list
decisionDecisões tomadas e alternativasalembic memory decision add|list
transcriptTurnos importados de sessões (A4b)alembic memory transcript add|list

O que dá segurança ao desenho é o binding: um employee declara QUAIS substores usa (employeeMemoryBindingSchema, employee.ts:67–73). Na hora do turno, composeMemoryText projeta os cinco readers para apenas os bindados — store não bindado vira reader no-op, "so its records never leak into the prompt" (run.ts:191–199). Memória é privilégio declarado, não ambiente compartilhado.

<dataDir>/memory/*.jsonl (append-only, escopo por agent) episodic.jsonl semantic.jsonl procedural.jsonl decision.jsonl transcript.jsonl BINDING do employee stores: [episodic, semantic] ✓ bindado → passa ✕ não bindado → reader no-op "never leak into the prompt" run.ts:191–199 composer lê os 5 readers e monta um bundle tipado ## Memory no system prompt
Os 5 substores (employee.ts:49–55) atrás do portão de binding: só o que o employee declara entra no composer e, daí, na seção ## Memory do prompt.
2

SkillStore: disclosure progressiva (e symlinks NÃO)

O SkillStore (packages/hermes/src/skills/skill-store.ts:68) é um clone fiel do subsistema de skills do Hermes original: cada skill é um diretório <base>/<nome>/SKILL.md (frontmatter + corpo). A economia de contexto vem da disclosure progressiva: list() lê SÓ o frontmatter de cada SKILL.md — nunca os corpos (skill-store.ts:75–96); view() carrega o corpo inteiro + arquivos de apoio quando você realmente precisa. Toda operação falível devolve Result e nunca lança pela borda pública (skill-store.ts:34–35).

fusao · 12-deep-skills · s0
Gotcha de produção O list() pula qualquer entrada que não seja diretório: if (!entry.isDirectory) continue; (skill-store.ts:89). Como o FsPort.readDir reporta um symlink com isDirectory: false, skill symlinkada sob a base não aparece — nem no list, nem no alembic import --skills (que usa o mesmo store). O acervo real de 148 skills em ~/.hermes/skills só conta diretórios reais com SKILL.md. Sintoma clássico: "importei meu diretório de skills e vieram menos" — os que faltam são links.

Ao lado do store vivem os templates determinísticos e offline: triageIssue (templates/issue-triage.ts:157) classifica uma issue e extractMeetingNotes (templates/meeting-notes.ts:164) estrutura um transcript — os comandos alembic triage e alembic notes, $0 por default.

3

A4b: importar sessões de OUTROS agentes

O pacote @alembic/sessions lê o histórico de três ecossistemas — readClaudeSession, readCodexSession, readChatGptExport (packages/sessions/src/index.ts:26–28) — e normaliza tudo em SessionRecord. Daí o hermes assume: sessionToTranscriptRecords (memory/transcript-import.ts:51) transforma cada turno num record de transcript com id determinístico import-<sessionId>-<n> e o at vindo da FONTE (nunca Date.now); appendNewTranscriptRecords (transcript-import.ts:122) deduplica por id contra o arquivo existente — reimportar é no-op. No CLI: alembic import <path>, dry-run $0 por default, --write para gravar.

Destilar sem fabricar (--distill --online)
A destilação de sessão em memória semantic/decision é interpretação de modelo — e fica marcada como tal, para sempre. Três mecanismos em memory/session-distill.ts: ① o prompt exige só o que está EXPLÍCITO no transcript — "NEVER invent, infer beyond the text, or add outside knowledge" (DISTILL_SYSTEM_PROMPT, linha 98); ② todo record carrega proveniência session:<id> + distilled:<modelId> (sessionAssociation/distilledAssociation, linhas 78–81), com tags falsificadas pelo modelo REMOVIDAS; ③ a confiança nasce rebaixada: DISTILLED_MEMORY_CONFIDENCE = 0.4 (linha 66) — abaixo do teto ≤ 0.5 da regra da casa. Resposta malformada/vazia do modelo → resultado VAZIO, nunca um record inventado. E --distill sem --online falha fechado: sem gasto, sem fabricação.
4

O kernel do AI Employee

Um employee é composição, não código novo (employeeDefinitionSchema, employee.ts:95–108): um soul (identidade + modelPreferences) + skills (ids no SkillStore) + memory binding + declarações de connectors e schedule. renderEmployeePrompt (employee.ts:208) transforma isso em system prompt; composeSkillsText (run.ts:281) injeta os corpos inteiros das skills bindadas como ## Skill Playbooks — o modelo SEGUE as regras da skill, não apenas vê nomes (run.ts:87–92).

produto · 0005-ai-employee · s1

O turno em si tem duas metades com temperamentos opostos: buildEmployeeRunInput (run.ts:112–138) é PURO e determinístico — sem clock, sem RNG, sem IO; requestId = employee-<id>; modelo = primary ?? fallback ?? fallbackModelId, senão err — e é ELE que o dry-run $0 imprime ("o prompt que seria enviado"). Já runEmployeeTurn (run.ts:151–189) faz UMA única chamada adapter.run(input) pela cintura estreita da lição 02; memória que falhar na composição degrada para '' — "memory must never break the turn" (run.ts:143–149).

goal --goal "…" buildEmployeeRunInput PURO: soul + playbooks + ## Memory (só bindados) dry-run $0 imprime AQUI e para adapter.run(input) UMA chamada · --online SEM tool-loop no meio texto EmployeeTurnResult A3b write-back episode = goal REAL context = excerto ≤500 só se binda episodic senão ok(false) no-op falha de escrita NUNCA quebra o turno (writeback.ts:102–107) · --no-memory-write opta fora · dry-run NUNCA escreve
runEmployeeTurn (run.ts:151–189) + recordEmployeeTurn (writeback.ts:74–108): uma chamada, um registro episódico honesto, zero fabricação.
Caveat de honestidade (provado na prática) O turno NÃO tem tool-loop: é uma única passagem modelo-adentro, modelo-afora. Pedir "varra o diretório X e liste o que achar" produz uma varredura FABRICADA — o modelo não tem ferramenta para varrer nada. Até a perna de tool-loop existir, dados verificados entram no prompt (via memória bindada, playbooks ou o próprio goal), nunca por suposta ação do employee. O write-back A3b registra o que de fato houve: o goal real e um excerto real da resposta (writeback.ts:88–95) — jamais um "learning" sintetizado.
5

Cheque seu modelo mental

Um employee SEM episodic no binding completa um turno --online com sucesso. O que o write-back A3b faz?
Correto: o write-back é opt-inwriteback.ts:79–81: sem episodic no binding, recordEmployeeTurn retorna ok(false) e nada é gravado. E mesmo quando grava, uma falha de escrita vira err logado sem quebrar o turno (o turno já foi um sucesso).
Você importa um diretório de skills cheio de symlinks com alembic import --skills --write. O que acontece com os links?
Correto: skill-store.ts:89if (!entry.isDirectory) continue;. O readDir do FsPort marca symlink como isDirectory: false, então só diretórios REAIS com SKILL.md entram. É por isso que plugins symlinkados "somem" do import — e por que os 148 skills contados são todos diretórios de verdade.
Camada técnica — comandos copy-paste

Tudo offline/$0 por default (o --online é a exceção deliberada):

# memória multi-store: ler e escrever na mão
alembic memory semantic list --agent demo --limit 5
alembic memory episodic add --agent demo --record '{"episode":"li a lição 03","context":"curso v3"}'

# o employee de ponta a ponta, sem gastar um centavo
alembic employee list
alembic employee show <id>
alembic employee run <id> --goal "resuma seu papel" # dry-run: imprime o prompt e PARA

# A4b: importar sessões (preview $0; --write grava idempotente)
alembic import ~/.claude/projects/<proj>/<sessao>.jsonl
alembic import conversations.json --format chatgpt --write

# o distill honesto exige as duas flags — e sem --online falha fechado
alembic import <sessao> --distill            # err: --distill needs --online

# a gotcha do symlink, na fonte
grep -n "entry.isDirectory) continue" packages/hermes/src/skills/skill-store.ts

Números da casa: 148 skills no SkillStore (~/.hermes/skills) · 43 AI Employees (~/.alembic/employees) · confiança de record destilado nasce em 0.4 (≤ 0.5 sempre).

O que levar desta lição
Pergunta de acompanhamento sugerida: "se o employee não tem tool-loop, quem executa trabalho de verdade com verificação?" — Exatamente a próxima lição: os gates de Coda/Mission/Forge, onde unit.proof[] roda comandos reais e falha a run fechada se a prova não sair verde.