Lição 07 · A herança Hermes · Alembic × Hermes v3 · Hermes: AI Employees (A1–A5)
Alembic × Hermes — O Curso de Fusão v3 · Visual Course

Hermes II — AI Employees: contratar, rodar e auditar um agente

Ao fim desta lição você sabe o que um dos 43 employees em ~/.alembic/employees É (composição, não mágica), como um turno vira UMA chamada de modelo auditável, e onde o engine se recusa — estruturalmente — a fabricar.

Você vai conseguir
  • Desenhar a anatomia de um employee (soul + skills + memory binding + declarações)
  • Explicar por que buildEmployeeRunInput é puro e por que isso é spend-safe
  • Contar o caveat de fabricação do turno sem tool-loop — e a mitigação
  • Descrever o write-back A3b honesto e o journal A4c (memory.md)
  • Usar explain lendo as tags observed / inferred / unknown
Leia a versão simples, ou abra a camada técnica em qualquer seção.
1

A grande ideia


Um AI Employee não é um subsistema novo — é uma camada fina de COMPOSIÇÃO sobre peças que o Alembic já tinha: a identidade (soul), as skills do SkillStore, o binding para os 5 substores de memória da lição 06, e DECLARAÇÕES de connectors e schedule que outras unidades (A3, A4) tornam executáveis. O cabeçalho do arquivo diz literalmente: "This file invents NONE of those: it composes them" (packages/hermes/src/employee/employee.ts:12-19).

Pense como… um dossiê de contratação: uma ficha com quem a pessoa é, o que sabe fazer, a que arquivos tem acesso e que crachás pediu. Contratar é preencher a ficha — os crachás (connectors) só abrem portas quando alguém os LIGA. A analogia quebra: aqui a ficha é um schema Zod que recusa qualquer campo malformado na fronteira.

produto · 0005-ai-employee · s0

O mapa A1–A5

A1 = a definição + renderEmployeePrompt (composição pura). A2 = a leitura de memória no turno (composer da lição 06, só substores BINDADOS). A3 = o seam de connectors (connectors.ts) — tipado, offline honesto. A3b = o write-back episódico. A4 = schedule → manifests de automation; A4c = o runner automation run (PR #159). A4b = importar sessões/skills de fora (lição 06). A5 fecha o domínio com a superfície CLI completa: alembic employee list|show|connectors|explain|run|schedule (apps/cli/src/args.ts:520).

2

Anatomia — o schema em uma imagem


Seis campos, todos validados por employeeDefinitionSchema (employee.ts:95-108). O soul vai EMBUTIDO verbatim; o resto são ids opacos resolvidos pelos subsistemas donos.

<id>.json — employeeDefinitionSchema id: "failure-historian" soul: { identidade, valores, modelPreferences… } skills: ["…", "…"] memory: { agentId, stores[] } connectors: ["slack", …] schedule: [{ task, cron }] soul embutido renderSoulPrompt lidera SkillStore (148 skills) corpos INTEIROS no prompt 5 substores (lição 06) só stores BINDADOS entram A3: seam de connectors declaração ≠ port ligado A4: → automation.toml cron opaco, nasce PAUSED 43 employees reais em ~/.alembic/employees — um JSON por employee, listados por listEmployees (junk é pulado)
Composição: cada campo aponta para o subsistema que já era dono daquela capacidade.
3

Prompt & preview spend-safe


Dois puros em sequência: renderEmployeePrompt monta o lead-in (soul → ## Skills## Connectors, seções vazias somem — employee.ts:208-219); buildEmployeeRunInput monta e VALIDA o ModelRunInput que SERIA enviado — sem clock, sem RNG, sem IO. É por isso que alembic employee run é um dry-run $0 por padrão: imprimir o preview não custa nada e é byte-idêntico a cada execução.

packages/hermes/src/employee/run.ts:112-138 (buildEmployeeRunInput)
// trecho real, não editado
const prefs = employee.soul.modelPreferences;
const modelId = prefs?.primary ?? prefs?.fallback ?? opts?.fallbackModelId;
if (modelId === undefined || modelId.length === 0) {
  return err(new Error(`employee ${employee.id} declares no model and no fallback was given`));
}

const systemPrompt = assembleSystemPrompt(employee, opts?.memoryContext, opts?.skillsContext);
const candidate = {
  requestId: `employee-${employee.id}`,   // derivado, nunca aleatório
  modelId,
  systemPrompt,
  userPrompt: goal,
  ...(prefs?.maxTokens === undefined ? {} : { maxOutputTokens: prefs.maxTokens }),
  ...(prefs?.temperature === undefined ? {} : { temperature: prefs.temperature }),
};
Cadeia de resolução do modelo: soul.modelPreferences.primary ?? fallback ?? fallbackModelId (na CLI, local-default). Sem nenhum dos três ⇒ err — um turno não roda sem modelo. Gotcha operacional: employee run não tem flag --model; o modelo que funciona TEM que estar no soul.
Camada técnica — comandos desta seção
alembic employee list                                  # os 43, um por linha
alembic employee show failure-historian                # definição + prompt renderizado
alembic employee run failure-historian --goal "resuma o dia"        # DRY-RUN $0: imprime o preview
alembic employee run failure-historian --goal "resuma o dia" --online  # founder-gated; falha fechado sem gateway
4

O turno — UMA chamada, memória que nunca quebra


runEmployeeTurn executa exatamente uma chamada ao ModelAdapter injetado, em 4 passos: compor memória (best-effort), compor playbooks das skills bindadas, construir+validar o input, chamar adapter.run (run.ts:151-189). Uma falha ao compor memória degrada para turno SEM memória — nunca derruba o turno. E os corpos das skills entram INTEIROS como ## Skill Playbooks, para o modelo SEGUIR as regras, não só ver os nomes.

## Memory (opt-in) só substores bindados ## Skill Playbooks corpos via SkillStore soul + skills + connectors renderEmployeePrompt buildEmployeeRunInput PURO · validado · $0 default: SÓ imprime (dry-run) --online adapter.run UMA chamada texto + usage A3b: recordEmployeeTurn 1 episódico honesto (opt-in) falha ao compor memória ⇒ turno segue SEM memória; falha do write-back ⇒ logada, turno JÁ venceu
Um turno = uma chamada auditável. O systemPrompt exato enviado volta no resultado (run.ts:74-75).
Caveat de honestidade — provado na fronteira

O turno não tem tool-loop. Pedir "varra o ledger X" a um employee --online produziu uma varredura FABRICADA plausível — o modelo não tem como ler arquivo nenhum (prova registrada no fechamento do A4c, commit 8d9ed2a). Mitigação documentada: dados VERIFICADOS vão dentro do prompt até a perna tool-loop existir. O engine não esconde a limitação — ele a documenta e a etiqueta no explain.

5

A3b — o write-back honesto


Depois de um turno --online bem-sucedido, o engine anexa UM registro episódico: o episode É o goal real; o context É um excerto de até 500 caracteres da resposta real. Nunca um "learning" inventado. O primeiro registro episódico real da história do engine nasceu assim, na prova de fronteira de 06–07/07.

packages/hermes/src/employee/writeback.ts:74-95 (recordEmployeeTurn)
// trecho real, não editado
const binding = resolveMemoryBinding(employee);
// Opt-in: write back ONLY when the employee explicitly binds the episodic store.
if (binding === undefined || !binding.stores.includes('episodic')) return ok(false);

const at = Date.parse(opts.now);            // clock INJETADO da CLI, nunca Date.now()
// …
const candidate: EpisodicRecord = {
  id: `writeback-${employee.id}-${at}`,    // derivado, nunca randomUUID()
  agent: binding.agent,
  at,
  episode: opts.goal,                        // o goal REAL
  context: excerpt(turn.text, WRITEBACK_CONTEXT_MAX),  // excerto REAL (500)
  salience: DEFAULT_EPISODIC_SALIENCE,
};
Um employee SEM binding episodic completa um turno --online com sucesso. O que o write-back A3b faz?
Opt-in estrutural: writeback.ts:79-81 — sem binding episodic, ok(false) e nada é escrito. E mesmo quando escreve, uma falha de append vira err logado que NUNCA quebra o chamador (o turno já venceu). --no-memory-write desliga; o dry-run offline nunca escreve.
6

A3 — o seam de connectors


resolveEmployeeConnectors projeta os ids declarados através de um ConnectorProvider: id resolvido vira port wired; id não resolvido vai para unwirednunca é descartado em silêncio. Ordem preservada, duplicatas colapsam, e wired + unwired = ids distintos declarados (connectors.ts:87-102). O provider offline é honesto por definição: resolve TUDO para undefined, então alembic employee connectors <id> reporta cada connector declarado como unwired (no adapter). Adapters reais (OAuth por serviço) são founder-gated e vivem FORA do seam.

O port é a cintura estreita de 4 verbos do modelo Supercomputer: read, list, write, post (connectors.ts:36) — e invoke nunca lança: falha de transporte/auth é err, como no ModelAdapter.

7

A4 + A4c — do cron declarado ao journal


A4: employeeToAutomations (camada CLI, para não criar aresta hermes→automation) mapeia cada schedule[i] para um manifest registrável: id determinístico emp-<id>-<i>, o cron carregado como rrule OPACO, status PAUSED (registrável mas nunca auto-roda), created_at/updated_at fixos em 0 para o mapa ser puro (apps/cli/src/employee-schedule.ts:39-48). A4c (PR #159) adicionou a perna que faltava: automation run <id> EXECUTA um manifest — sem virar daemon.

produto · 0005-ai-employee · s2
schedule[] {task, cron} declarado A4 mapper emp-<id>-<i>/automation.toml status PAUSED · rrule opaco modelo do soul · created_at 0 spend-safe: nunca auto-roda A4c (PR #159) automation run <id> default: DRY-RUN $0 (preview do ModelRunInput, sem chamada, sem journal) --online memory.md 1 registro honesto/run journal = @alembic/automation AutomationJournal.appendRun em <dir>/<id>/memory.md (JOURNAL_FILENAME, journal.ts:35) falha ao escrever o journal ⇒ warn 'automation:journal-write-failed' — o run já aconteceu, nunca quebra
Boundary-prova do A4c: o manifest real emp-failure-historian-0 rodou --online e PARIU o primeiro registro do journal.
Camada técnica — comandos desta seção
alembic employee schedule failure-historian                 # A4: imprime os manifests (não escreve)
alembic employee schedule failure-historian --out ~/.codex/automations   # escreve automation.toml
alembic automation list --dir ~/.codex/automations          # lê de volta o que o A4 escreveu
alembic automation run emp-failure-historian-0 --dir ~/.codex/automations           # dry-run $0
alembic automation run emp-failure-historian-0 --dir ~/.codex/automations --online  # roda + journal
8

explain — o mapa que não fabrica


explainEmployeeExecution responde "como ESTE employee transformaria ESTE goal em ação verificável" nos 10 passos canônicos: message-to-task → context-assembly → planning → tool-use → verification → output → memory-update → report → next-loop → failure-recovery (introspect.ts:36-47). A regra de ouro é ESTRUTURAL: cada claim carrega uma tag — observed (lido da config deste employee), inferred (default real do engine) ou unknown (um interno de verdade não inspecionável). O mapa nunca afirma que A3/A4 "está operacional" sem estar: esses caveats vão verbatim para os ledgers inferred/unknown.

Num turno --online você pede: "varra o ledger X e liste os erros". O que a prova de fronteira mostrou?
Provado e documentado no fechamento do A4c (commit 8d9ed2a): sem tool-loop, o modelo inventou a varredura. Por isso o caveat operacional: dados verificados entram NO prompt até a perna tool-loop nascer — e o explain mantém tool-use como inferred/unknown, nunca observed.
9

Experimente


Quatro cartas de retrieval antes do próximo módulo.

composição
O que um employee INVENTA de novo no engine?
clique para virar
Nada. Ele COMPÕE soul + skills + memory binding + declarações; cada peça já tinha dono (employee.ts:12-19).
spend-safe
Por que o dry-run do employee run custa $0 sempre?
clique para virar
Porque buildEmployeeRunInput é PURO (sem IO/clock/RNG) — montar e imprimir o preview não chama modelo nenhum (run.ts:112-138).
a3
O que acontece com um connector declarado e não resolvido?
clique para virar
Vai para unwired — nunca é dropado em silêncio. Offline, TODOS caem aí (provider honesto devolve undefined).
a4
Por que todo manifest mapeado nasce PAUSED?
clique para virar
Spend-safe: registrável mas nunca auto-roda; só roda à mão via automation run. E created_at/updated_at = 0 mantêm o mapa puro.
Pergunta para levar: qual seria o desenho MÍNIMO de um tool-loop que preservasse o anti-fabricação estrutural do turno? Próxima lição: o cérebro deliberativo — como o Council empacota evidência (L0–L7) e decide GO/NO_GO.