Lição 06 · Alembic Completo v3 · Domínio AI-Employee · A1–A5
Alembic Completo v3 · Visual Course

AI Employees: o domínio A1–A5

Ao fim desta lição você sabe o que É um AI Employee no Alembic (composição, não mágica), o que cada um dos 6 subcomandos faz, por que o explain nunca fabrica, e onde exatamente o motor grava o seu primeiro registro honesto de memória.

Leia primeiro (fonte primária)
packages/hermes/src/employee/ — os 5 módulos do domínio

Esta lição destila employee.ts, connectors.ts, introspect.ts, run.ts e writeback.ts — a camada que transforma capabilities soltas num "funcionário" executável e auditável.

Você vai conseguir
  • Desenhar a anatomia de um employee (soul + skills + memory + connectors + schedule)
  • Explicar as tags observed / inferred / unknown do explain
  • Distinguir o dry-run $0 do --online e onde entra o A3b write-back
  • Mapear schedule → manifests PAUSED (A4) e o journal do A4c
  • Enunciar o caveat de fabricação sem tool-loop — e a regra que o neutraliza
Leia a versão simples, ou abra a camada técnica em qualquer seção.
1

A grande ideia — composição, não mágica


Um AI Employee do Alembic não é um novo runtime: é uma camada de composição sobre coisas que o motor já tinha. O schema diz tudo — employeeDefinitionSchema (packages/hermes/src/employee/employee.ts:95) compõe: um soul (identidade + modelPreferences), uma lista de skills (nomes resolvidos no SkillStore), um memory binding opcional (quais dos 5 substores este funcionário lê), e DECLARAÇÕES de connectors e schedule — que outros seams (A3, A4) fiam depois. O comentário do próprio arquivo: "it adds no behaviour to any of them".

O acervo real de hoje: 43 AI Employees em ~/.alembic/employees e 148 skills no SkillStore ~/.hermes/skills.

Pense como… uma ficha de contratação: nome e valores (soul), cursos que a pessoa fez (skills), o caderno que ela pode consultar (memory binding), os crachás de acesso que ela PEDIU mas ainda não recebeu (connectors) e a agenda combinada (schedule). A analogia quebra num ponto: o funcionário de verdade lembra sozinho — aqui, só entra na cabeça dele o substore explicitamente bindado.

43
AI Employees (~/.alembic/employees)
148
skills no SkillStore (~/.hermes/skills)
5
substores bindáveis (employee.ts:49)
6
subcomandos employee no CLI

Por baixo do capô

employeeMemoryStores (employee.ts:49) fixa o vocabulário dos substores: episodic | semantic | procedural | decision | transcript — o MESMO da memória multi-store, então o binding é type-checked contra os stores reais. loadEmployee (employee.ts:147) valida um <id>.json fail-closed (leitura, JSON e schema, cada falha vira err de uma linha); listEmployees (employee.ts:180) pula arquivo ruim sem derrubar a listagem e ordena por nome para saída determinística.

2

Em uma imagem — a anatomia vira prompt


Tudo que o employee É converge para UMA string: o system prompt do turno. renderEmployeePrompt (employee.ts:208) lidera com o soul e lista skills/connectors; assembleSystemPrompt (run.ts:249) apensa ## Skill Playbooks (os CORPOS das skills) e ## Memory (só substores bindados) — playbooks antes da memória: conhecimento operacional primeiro, recall por turno depois.

employee.json (employeeDefinitionSchema — employee.ts:95) soul (identidade) modelPreferences.primary/fallback skills[] → SkillStore memory binding (5 substores) connectors[] (DECLARAÇÃO · A3) schedule[] (DECLARAÇÃO · A4) system prompt soul → ## Skills → ## Connectors + ## Skill Playbooks (corpos) + ## Memory (só o bindado) ModelAdapter a cintura estreita tracejado = declaração ainda sem runtime (A3 connectors · A4 scheduler) — nunca vendida como operacional
As 5 partes do employee.json convergem no system prompt (run.ts:249 assembleSystemPrompt); declarações tracejadas não entram como comportamento — só como texto declarado.
3

Os 6 subcomandos


Toda a superfície do domínio no CLI é alembic employee <ação>. Seis ações, todas offline/$0 por default — só run --online chama modelo.

subcomandoo que fazonde no código
list1 linha por employee (id/nome/papel/#skills/#connectors)commands.ts · runEmployeeList
showdefinição completa + o renderEmployeePrompt renderizadocommands.ts · runEmployeeShow
connectorsresolve declarados → wired vs unwired (nunca dropa em silêncio)connectors.ts:87 · resolveEmployeeConnectors
explainmapa dos 10 passos com tags observed/inferred/unknownintrospect.ts:476 · explainEmployeeExecution
run1 turno: preview $0 default; --online chama o modelorun.ts:151 · runEmployeeTurn
scheduleschedule[] → manifests automation.toml PAUSEDapps/cli/src/employee-schedule.ts:111
Honestidade estrutural nos connectors: o provider default é o offlineConnectorProvider (connectors.ts:111) — ele resolve NADA de propósito. Todo connector declarado aparece como unwired (no adapter): o seam A3 existe, os adapters reais (OAuth por serviço) são founder-gated e vivem fora dele.
Camada técnica — copy-paste
# os 6 subcomandos (todos $0; run só gasta com --online)
alembic employee list
alembic employee show failure-historian
alembic employee connectors failure-historian --json
alembic employee explain failure-historian --goal "audite os runs parados"
alembic employee run failure-historian --goal "audite os runs parados"
alembic employee schedule failure-historian --json
4

explain — anti-fabricação estrutural


explain responde "como ESTE employee transformaria ESTE goal em ação verificável" — em 10 passos canônicos (executionStepKindSchema, introspect.ts:36): message-to-task → context-assembly → planning → tool-use → verification → output → memory-update → report → next-loop → failure-recovery. O truque não é o mapa: é que cada claim carrega uma tag de confiança (introspect.ts:55).

observed lido da config DESTE employee · inferred comportamento default real do motor · unknown um interno verdadeiro não-inspecionável (ex.: o CONTEÚDO de um store).

produto · 0005-ai-employee · s2

Pense como… um laudo pericial: o perito separa "eu vi" (observed), "pela mecânica, deve ter sido assim" (inferred) e "não dá para saber daqui" (unknown). Quebra onde? O perito pode errar a tag; aqui a tag é imposta pelo schema — um claim sem tag nem compila.

executionStepKindSchema (introspect.ts:36) — a ordem em que um run real executa 1 message-to-task 2 context-assembly 3 planning 4 tool-usecaveat A3 se unwired 5 verification 6 output 7 memory-updateA3b se write ligado 8 report 9 next-loopcaveat A4 se sem runner 10 failure-recovery observed inferred unknown cada passo carrega UMA tag — o schema exige (introspect.ts:65) GROUND_RULE fixo em todo relatório (introspect.ts:135): "nothing is fabricated"
Os 10 passos e as 3 tags. Passos 4 e 9 carregam caveats "A3/A4 não construído" — que só caem quando o caller PROVA o contrário (ExplainOptions.wiredConnectors / scheduleWired).

Nunca vende subsistema não-construído

O relatório embute o GROUND_RULE (introspect.ts:135) como parte do ARTEFATO. E os caveats caem por prova, não por otimismo: ExplainOptions (introspect.ts:94-129) só remove o caveat "A3 not built" para os ids em wiredConnectors, o "A4 runner not built" quando scheduleWired: true, e o "write off-by-default" quando memoryWriteEnabled: true — e mesmo assim o claim continua inferred (a execução futura não é fato observado). explainEmployeeExecution e renderExecutionChainReport são PUROS/determinísticos: mesmo employee + goal ⇒ relatório byte-idêntico.

5

run — dry-run $0 vs --online


alembic employee run <id> --goal "…" tem DOIS modos, e o default nunca gasta. Sem flag: runEmployeeRunOffline (apps/cli/src/commands.ts:5163) monta via buildEmployeeRunInput (run.ts:112, PURO/determinístico — requestId = employee-<id>) e IMPRIME o preview spend-safe: o prompt que SERIA enviado + o modelId resolvido (primary ?? fallback ?? local-default; sem nenhum dos três ⇒ err, run.ts:119). NENHUM modelo é chamado.

Com --online (founder-gated): runEmployeeTurnOnline (commands.ts:5219) constrói o adapter real do gateway com preflight — gateway/token ausente ⇒ falha fechada, SEM gasto — e o turno vira runEmployeeTurn (run.ts:151): compõe memória (best-effort, memória nunca quebra o turno), compõe playbooks, chama adapter.run(input) e valida o resultado.

Skills viram REGRAS, não nomes
composeSkillsText (run.ts:281) carrega o CORPO de cada skill bindada via SkillStore e injeta como ## Skill Playbooks. Best-effort: skill que falha ao carregar é PULADA em silêncio — guarde isso; vira a lição dos bindings-fantasma na Lição 10.
Store não bindado NUNCA vaza
selectBoundStores (run.ts:228) troca cada substore não-bindado pelo EMPTY_READER (run.ts:240) — o composer exige as 5 chaves, então a neutralização é estrutural, não um "if" esquecível.
Camada técnica — copy-paste
# dry-run (default): imprime system prompt + goal + modelId; NÃO chama modelo
alembic employee run failure-historian --goal "resuma os piores ofensores da semana"

# online (founder-gated): preflight do gateway; falha fechada sem token
alembic employee run failure-historian --goal "resuma os piores ofensores da semana" --online

# online SEM write-back episódico (A3b off por flag)
alembic employee run failure-historian --goal "…" --online --no-memory-write
6

A3b — o write-back honesto


Depois de um turno --online bem-sucedido, recordEmployeeTurn (writeback.ts:74) apensa UM registro episódico honesto: episode = o goal REAL verbatim (writeback.ts:92), context = excerto REAL da resposta do modelo, truncado em WRITEBACK_CONTEXT_MAX = 500 chars (writeback.ts:50). NUNCA um "learning" fabricado. Opt-in: sem binding episodic ⇒ no-op ok(false) (writeback.ts:81). Determinístico: at vem do clock injetado do CLI; id = writeback-<employeeId>-<at> (writeback.ts:89). E uma falha de escrita NUNCA quebra o turno (writeback.ts:102-107).

Isso não é teoria: o primeiro registro episódico da história do motor foi gravado em 06-07, pelo employee failure-historian. Ele está em ~/.alembic/memory/episodic.jsonl — exatamente 1 linha, com o formato de id do writeback.ts:

~/.alembic/memory/episodic.jsonl — o 1º registro real (recortado)
{"id":"writeback-failure-historian-1783331936178",
 "agent":"failure-historian","at":1783331936178,
 "episode":"Primeiro digest real do motor. DADOS VERIFICADOS (auditoria 06-07): …
   (4) classe recorrente ABERTA: drift-de-contagem … contagens escritas a mao
   e nunca re-derivadas da fonte. …",
 "context":"# Repeat-Offender Digest\n\n**Estado do Catálogo:** Inicializad…"}
Leia o registro de novo: episode é o goal que o operador realmente passou (um digest com dados VERIFICADOS no prompt) e context é o começo da resposta real do modelo. Nada resumido por uma segunda IA, nada inventado. É a materialização da regra do writeback.ts: "It records the REAL turn, never an invented learning".
Quiz — o que o A3b grava após um turno --online bem-sucedido?
Certo: 1 registro episódico honesto — episode = goal verbatim, context = excerto ≤500 chars da resposta real (writeback.ts:88-95). Só no substore episodic, só se bindado, e uma falha de escrita nunca quebra o turno.
7

A4 + A4c — agenda vira manifest; manifest roda e registra


A4: alembic employee schedule <id> mapeia cada schedule[i] = {task, cron} para um manifest @alembic/automation via employeeToAutomations (apps/cli/src/employee-schedule.ts:111 — PURO, fail-closed). Id determinístico emp-<id>-<i>, o cron viaja como rrule opaco, e o status nasce PAUSED (employee-schedule.ts:42) — registrável mas spend-safe: nunca auto-roda. Default = IMPRIMIR; --out <dir> escreve <dir>/<id>/automation.toml, o exato formato que alembic automation list --dir relê.

A4c (PR #159): alembic automation run <id> EXECUTA um manifest — dry-run default; --online roda o turno e apensa 1 registro honesto ao journal memory.md da automation. O journal já é um tipo de primeira classe: JOURNAL_FILENAME = 'memory.md' + AutomationJournal append-only (packages/automation/src/journal.ts:35) — o análogo tipado do memory.md que as automations codex acumulam por run. O 1º registro real desse journal também é de 06-07. Não é daemon: PAUSED só roda à mão.

Pense como… pendurar o crachá do funcionário no relógio de ponto (A4) com o ponteiro TRAVADO em pausa — e o A4c é você girar a manivela UMA vez, à mão, e o relógio carimbar a ficha (journal). A analogia quebra: relógio de verdade anda sozinho; aqui, de propósito, não existe daemon.

produto · 0005-ai-employee · s3
Camada técnica — copy-paste
# A4: preview dos manifests (nada é escrito)
alembic employee schedule failure-historian

# A4: materializa em disco no formato que automation list relê
alembic employee schedule failure-historian --out ~/.alembic/automations
alembic automation list --dir ~/.alembic/automations
alembic automation show emp-failure-historian-0 --dir ~/.alembic/automations

# A4c (PR #159): executa 1 manifest — dry-run default; --online registra no journal memory.md
alembic automation run emp-failure-historian-0 --dir ~/.alembic/automations
alembic automation run emp-failure-historian-0 --dir ~/.alembic/automations --online
8

O caveat de fabricação — e a regra que salva


Honestidade final, provada na fronteira: o turno do employee NÃO tem tool-loop. O modelo recebe prompt e devolve texto — ele não pode LER arquivo, rodar comando, varrer diretório. Se o goal pede "varra X e liste o que achar", a resposta vem com uma varredura FABRICADA (isso foi testado e provado; está no spec do curso como caveat oficial).

A regra operacional até a perna tool-loop existir: dados verificados vão NO PROMPT. É exatamente o que o goal do 1º registro episódico fez — repare no texto real: "DADOS VERIFICADOS (auditoria 06-07): (1)… (2)… (3)…". O operador auditou fora, colou os fatos, e pediu ao modelo só o que modelo faz bem: sintetizar e propor.

errado — convida a fabricação
--goal "varra ~/.alembic/runs e
liste as tasks t4-parked"
O modelo não tem como varrer; ele INVENTA uma lista plausível.
certo — dados verificados no prompt
--goal "DADOS VERIFICADOS: 2 tasks
t4-parked (run-617ca163, run-1d9e285b).
Proponha UMA mudança de sistema."
A verdade entra pronta; o modelo sintetiza em cima de fato.
Recall da lição: employee = composição declarada (1) que vira um prompt auditável (2), explicável passo a passo sem fabricação (4), executável com $0 por default (5), que registra a verdade do turno (6), agenda sem daemon (7) — e cujo único buraco conhecido (sem tool-loop) tem regra de contorno explícita (8).
Pergunta que vale fazer agora: "se o modelo não pode varrer nada, QUEM produz os dados verificados?" — resposta na Lição 07: o funil determinístico e a rota Learnings, que em 06-07 produziu seus primeiros 1.022 learnings reais.