Você já destilou o corpus, forjou o signal, rodou a campanha da C.D Advocacia. Falta contratar quem opera o atendimento todo dia. Ao fim desta lição você sabe como o Alembic compõe uma persona — a Iris — a partir de soul + skills + memória + connectors + agenda, e como o comando explain te entrega um mapa onde nenhuma afirmação é inventada.
O cabeçalho deste arquivo diz tudo: o AI Employee não inventa identidade, memória ou skills — ele compõe o que já existia solto. Esta lição destila isso na Iris, a funcionária que fecha a história do curso (do corpus à operação real).
Result<T, Error>).alembic <comando>. Nada além disso.explain e classificar cada passo em observed / inferred / unknown.O Alembic já tinha todas as peças de uma funcionária digital — espalhadas: uma identidade (o soul-loader), uma memória de cinco gavetas (o multi-store), um catálogo de skills, um sistema de automação agendada, e os adapters de modelo. O que faltava era o conceito que amarra tudo numa pessoa só.
O AI Employee é esse conceito. A Iris é a primeira: uma especialista de atendimento que compõe essas peças. Ela tem uma identidade (quem é, o que valoriza), as skills que sabe rodar (ex.: triagem de tickets), uma memória que ela lê e na qual escreve, declarações de quais integrações usa (Gmail, Slack) e uma agenda (varrer a caixa toda manhã).
Pense como… contratar uma pessoa de verdade. Você não constrói um cérebro do zero — você combina uma personalidade, um currículo (skills), a memória do que já viveu, os crachás de acesso aos sistemas (connectors) e um horário de trabalho. Onde a analogia quebra: a Iris é um arquivo iris.json de texto — versionável, auditável, e por padrão sem nenhum crachá realmente ativado (offline, honesto).
O pacote é @alembic/hermes, módulo src/employee/. O contrato central é o employeeDefinitionSchema (Zod): ele embute o soulDefinitionSchema já existente e adiciona skills: string[], um memory binding opcional (sobre os 5 substores), connectors: string[] e schedule: {task, cron}[]. O arquivo inventa nenhum desses subsistemas — connectors e schedule são declarações (forward declarations) cabeadas depois nos seams A3 e A4.
Idioma idêntico ao resto do engine: loadEmployee lê via uma porta SoulReader injetada, faz JSON.parse, valida no boundary e nunca lança — uma leitura falha, JSON malformado ou um schema violado vira um err de uma linha. renderEmployeePrompt é puro e determinístico: lidera com o soul e anexa as seções ## Skills e ## Connectors quando não-vazias.
Por que isso importa: a Fase 7 (PRs #95–#99, #102) costurou skills + memória + scheduler + personas — peças já construídas soltas — no "maestro" que faltava. É a maior alavancagem do produto, segundo o próprio REVIEW.md.
Cinco ingredientes entram; uma persona contratável sai. A maestrina no centro não toca nenhum instrumento — ela rege os que já existiam.
corpus de IA jurídica → signal "fábrica de petições" → venture → campanha da C.D Advocacia → a Iris, que opera o atendimento dessa venture todo dia.Vire cada cartão para fixar o que cada ingrediente é — e o que ele não é (porque dois deles são só declarações até um seam cabear).
modelPreferences (modelo preferido). renderSoulPrompt a transforma no início do system prompt: "You are Iris, …".issue-triage). Quem resolve cada id é o SkillStore, não o employee. A Iris compõe; não reimplementa.agentId. Uma gaveta não ligada nunca vaza para o prompt.unwired.{task, cron} — ex.: "daily inbox sweep" às 0 9 * * *. O cron é opaco em A1; um runner só é cabeado em A4 (via mapeamento para @alembic/automation).Três dos cinco ingredientes já funcionam na composição. Dois são promessas honestas até um seam cabear. A tabela deixa explícito:
| Ingrediente | Estado em A1 | Cabeado por | O que muda quando cabeia |
|---|---|---|---|
| soul | Funciona (embutido) | — | — |
| skills[] | Ids resolvidos pelo SkillStore | — | — |
| memory | Binding lido; read em A2 | A3b (write-back) | Passa a escrever de volta, não só ler |
| connectors[] | Declaração (todo unwired) | A3 (connector seam) | Id resolve para um port real; deixa de ser só nome |
| schedule[] | Declaração (cron opaco) | A4 (mapper) | Vira manifest do automation; o daemon cron roda |
cron e o nome do connector como escalares opacos antes de existir um runner — exatamente como o subsistema de automação já carrega um rrule antes do seu daemon. Prometer sem fingir que já roda é a regra.O soul é a peça mais antiga: um adapt do soul-loader do Agent Swarm, recast no idioma Alembic (Zod no boundary, Result, porta injetada, nunca lança).
Aqui está a joia da coroa. Antes de gastar um centavo, você pode pedir um mapa de como a Iris transformaria um objetivo numa ação verificável — passo a passo. E a regra anti-fabricação é estrutural: cada um dos 10 passos é obrigado a carregar uma etiqueta.
Pense como… um raio-X de um motor antes de ligá-lo. Você vê cada engrenagem e, em cada uma, uma cor honesta: verde = "isto eu li na config da Iris", azul = "isto é o comportamento padrão do motor, não a Iris", vermelho = "isto é um interno que não dá pra inspecionar daqui". Onde quebra: não é opinião — o tipo Confidence no código força uma das três cores em cada passo.
Dos 10 passos do mapa da Iris, quantos você acha que ganham a etiqueta observed (lido direto da config dela)?
planning (porque a Iris fixa um modelo em modelPreferences.primary) e memory-update (porque ela liga gavetas de memória) são observed. Os outros 8 são inferred — comportamento padrão do motor, não pinado pela Iris. É contraintuitivo e é de propósito: o mapa se recusa a chamar de "fato observado" qualquer coisa que ele só infere.Não é arbitrário. Cada construtor de passo no código decide a cor a partir de um fato verificável. Os três casos mais instrutivos:
A Iris fixa modelPreferences.primary = claude-opus-4-8. Esse é um fato da definição, então o passo é verde. Mas o código é cuidadoso: a cor reflete o modelo pinado, não que um plano já foi produzido (o plano é futuro).
As skills e connectors declarados são inputs observados (aparecem em reads), mas a invocação não aconteceu. O passo descreve a chamada pretendida ("would invoke") e nunca afirma que um connector disparou.
O binding (quais gavetas) é observado. Mas o que está dentro das gavetas só se sabe lendo em runtime — então isso vai para o ledger unknown, não para o passo.
A agenda é um input observado, mas o runner que a executa (A4) não está construído por padrão. O mapa jamais diz que um subsistema não-built está operacional.
Este é o explain em miniatura. Clique cada engrenagem para ver o que ela lê, o que faz e por que ganha aquela cor. Depois, revele todas as etiquetas de uma vez.
Cada passo mostra: component (a peça real do motor), reads (o que consome), does e por que ganha sua etiqueta de confiança.
confidenceSchema = z.enum(['observed','inferred','unknown']) e executionChainStepSchema exige um campo confidence em todo passo. Não há como um passo existir sem etiqueta — é por isso que a anti-fabricação é "estrutural", não "advisory". A constante GROUND_RULE é embutida em cada relatório, tornando o contrato parte do próprio artefato.explainEmployeeExecution(employee, goal, opts) é puro e determinístico (sem clock, sem RNG): roda uma tabela ordenada de STEP_BUILDERS (um por passo), cada um produzindo o passo + os caveats que ele levanta. Os caveats são coletados (de-duplicados, preservando a ordem) nos ledgers inferred e unknown.
Os seams têm flags que derrubam caveats sem mudar a confiança: wiredConnectors (A3) faz o passo tool-use parar de dizer "A3 não-built" para os connectors resolvidos; scheduleWired (A4) derruba o caveat do runner; memoryWriteEnabled (A3b) derruba o "off-by-default". Mas o passo permanece inferred — porque no instante do explain a chamada ainda é futura.
O comando CLI employee explain mantém o comportamento byte-idêntico (não força os flips); os flips são exercitados nos testes do introspect e no comando employee schedule.
O explain te dá o mapa de "would". O run transforma esse "would" numa execução real — mas com uma trava de segurança no centro: por padrão, ele monta o prompt e te mostra, sem chamar nenhum modelo. Custo: $0.
alembic employee run iris --goal "rascunhe as respostas da caixa desta semana".loadEmployee lê iris.json, valida no boundary. Falha vira err fail-closed.buildEmployeeRunInput (puro, sem IO) monta o ModelRunInput: system = renderEmployeePrompt (soul + skills + connectors); userPrompt = o goal.soul.modelPreferences.primary ?? fallback ?? local-default. Sem modelo e sem fallback → err (um turno não roda sem modelo).--online (founder-gated) — e por que ele falha fechado quando o gateway está ausente.O que aparece no terminal — a prova real, verbatim do RESOURCES.md:
# dry-run preview — NENHUM modelo chamado employee: iris (dry-run preview — no model called) model claude-opus-4-8 --- system (would be sent) --- You are Iris, customer support specialist. ## Core Values … ## Skills - issue-triage --- goal (user prompt) --- …
buildEmployeeRunInput é uma função pura que devolve o input, não o resultado. Gastar exige --online explícito.Duas superfícies em run.ts: buildEmployeeRunInput (pura/determinística — o preview que o dry-run imprime) e runEmployeeTurn (o turno ao vivo). O turno ao vivo compõe a memória best-effort: uma falha ao compor nunca quebra o turno (memória degrada para vazio). Só as gavetas que a Iris liga são lidas — uma gaveta não-ligada recebe um EMPTY_READER, então seus registros nunca vazam para o prompt.
--online constrói o adapter real cliproxyapi, preflightado: se o gateway/token está ausente, falha fechado sem gasto. O modelo vem de modelPreferences.primary; o sampling, das mesmas preferências.
O requestId é derivado deterministicamente do id do employee, então re-rodar dá um input byte-idêntico. (No fixture mínimo de teste, sem modelPreferences, o modelo cai no fallback local-default e o prompt começa "You are Iris, support specialist." — a Iris documentada, com modelo pinado, mostra claude-opus-4-8.)
Lembra que connectors e schedule são declarações? Aqui está como o Alembic os torna runnable sem mentir. Cada um tem um seam: um contrato tipado que separa "o que foi declarado" de "o que está de fato cabeado".
O comando employee connectors iris pega cada connector declarado e pergunta a um provider: "você tem um adapter pra isto?". Offline, o provider honesto responde não para todos. Resultado: Gmail e Slack aparecem como unwired (no adapter) — nada é silenciosamente descartado, nada finge estar vivo.
resolveEmployeeConnectors(employee, provider) é puro: mapeia cada id declarado através do ConnectorProvider, roteando um id resolvido para wired e um não-resolvido para unwired. Ordem preservada, duplicatas colapsam, e wired.length + unwired.length = nº de ids distintos. O offlineConnectorProvider resolve tudo para undefined. Adapters reais (OAuth/chaves por serviço) são founder-gated e vivem fora do seam.
employee: iris connectors (offline — no adapters wired) gmail unwired (no adapter) slack unwired (no adapter) note: real connector adapters (per-service OAuth/keys) are founder-gated.
emp-iris-0 [PAUSED] 0 9 * * *
claude-opus-4-8
task: daily inbox sweep
O id é determinístico (emp-<id>-<i>); o status nasce PAUSED — registrável, mas nunca auto-roda até um humano ativar. Spend-safe por default.
wired ou unwired. Offline, o provider honesto manda tudo para unwired. Os adapters reais entram por fora, founder-gated.unwired não é um bug — é o estado honesto. O Alembic se recusa a fingir que um Gmail está conectado quando não há OAuth. Se você precisa de envio real, isso é uma ação founder-gated, fora do seam, deliberadamente.schedule[i] vira um manifest registrável do @alembic/automation, nascido PAUSED. O cron é carregado como rrule opaco; o daemon existente o interpreta.A lição 3 mostrou a memória sendo lida para dentro do prompt. O write-back (A3b) fecha o loop: depois de um turno bem-sucedido e online, a Iris escreve de volta um registro honesto na sua gaveta episódica. É o "brief once, remembers" — você explica uma vez, ela lembra na próxima.
Pense como… um diário de bordo que a funcionária preenche ao fim do expediente. Onde quebra: o registro é deliberadamente anti-fabricação — ele grava o objetivo real que ela recebeu + um trecho real da resposta do modelo, nunca um "aprendizado" inventado.
episodic (senão é no-op ok(false)); (3) é determinístico — timestamp é o clock injetado, id derivado, sem Date.now()/randomUUID(); (4) nunca quebra o caller — uma falha de escrita é um err de uma linha; o turno já tinha sucesso."It records the REAL turn, never an invented learning. The episode IS the goal the employee was given; the context IS a truncated excerpt of the model's actual text." — verbatim do cabeçalho.
Seis slides, a história da Iris em sequência. Use as setas ← → ou os botões.
Três perguntas que amarram a lição. A pontuação corre embaixo.
employee.ts é explícito: "This file invents NONE of those: it composes them." Ele embute o soul, reusa o enum do multi-store e trata skills/connectors como ids opacos.explain da Iris, por que tool-use é inferred e não observed?reads (fatos observados), mas o passo descreve a chamada pretendida ("would invoke") — nunca afirma que um connector disparou. Por isso a confiança fica inferred.employee run iris sem --online. O que acontece com o modelo?buildEmployeeRunInput é puro: devolve o input, não o resultado. O dry-run imprime o preview spend-safe. O write-back só ocorre num turno --online bem-sucedido.iris.json — versionável, auditável, legível.renderEmployeePrompt = soul + ## Skills + ## Connectors, puro e determinístico.explain etiqueta todo passo: observed / inferred / unknown. Estrutural, não opcional.run é dry-run $0 por padrão; gastar exige --online explícito e preflightado.unwired — honesto, não bug. Adapters reais são founder-gated.PAUSED; nunca auto-roda até um humano ativar.Você já mexeu no mapa interativo da seção 4 — aquele é o explain em miniatura. Agora rode os comandos reais, offline, custo zero. Comece pelos que apenas leem:
alembic employee list → vê a Iris: iris Iris (customer support specialist) skills:2 connectors:2.alembic employee show iris → a definição completa + o prompt renderizado.alembic employee connectors iris → Gmail e Slack como unwired (no adapter).alembic employee explain iris --goal "responder os tickets de hoje" → o mapa de 10 passos. Conte: 2 observed, ledgers 2/2.alembic employee run iris --goal "rascunhe as respostas da caixa" → o preview spend-safe. Nenhum modelo chamado.employee explain com dois goals diferentes. Note que a estrutura dos 10 passos não muda — só o texto do goal. Isso é o determinismo em ação: a mesma Iris + mesmo goal sempre dá um relatório byte-idêntico.--online). Próxima lição → voltamos um passo na história: a Marketing Factory que gerou a campanha da C.D Advocacia que a Iris agora opera — com a mesma trava de gasto ($0 default, --approve --yes para gastar).