Curso · produto
Alembic — AI Employee (o maestro)
AI Employee — o maestro
Um AI Employee no Alembic não é um agente novo: é a camada de composição que reúne quatro peças que o motor já tinha — soul (identidade), skills, memória multi-store e connectors+schedule — numa persona única, contratável, introspectável e executável. O conceito vem do "Higgsfield Supercomputer"; o que muda no Alembic é que a composição é estrutural e anti-fabricadora: cada afirmação carrega um selo (observed / inferred / unknown) que diz exatamente de onde veio.
O que a composição une
Lendo packages/hermes/src/employee/employee.ts, o employeeDefinitionSchema é literalmente isto e nada mais — id, um soul embutido (o mesmo soulDefinitionSchema de memory/multi-store/soul.ts), um array de skills[] (ids opacos resolvidos pelo SkillStore), um memory binding opcional ({agentId, stores: ('episodic'|'semantic'|'procedural'|'decision'|'transcript')[]}), uma lista de connectors[] (apenas nomes em A1) e schedule[] ({task, cron} opacos). Não há comportamento novo — é DI pura sobre peças que já existiam.
Listar e inspecionar (read-only, $0)
$ alembic employee list
iris Iris (customer support specialist) skills:2 connectors:2
O employee list chama listEmployees sobre --dir (default ~/.alembic/employees), filtra *.json, pula .DS_Store e dotfiles, e tolera arquivos quebrados — um JSON inválido é silenciosamente ignorado em vez de derrubar a listagem. Tudo via um EmployeeDirReader injetado, o que torna a listagem testável sem tocar disco.
O mapa de 10 passos (employee explain)
employee explain é o reverse-engineer map do agente. Dado o EmployeeDefinition + um goal, explainEmployeeExecution deriva 10 passos canônicos — a regra do motor, não uma sugestão — e cada passo carrega um selo de confiança:
$ alembic employee explain iris --goal "triage today's inbox"
# Execution chain — iris
**Goal:** triage today's inbox
**Ground rule:** Derived only from this employee's observable definition + the
engine's real components. Inference is tagged `inferred`; true internals not
inspectable are marked `unknown`; nothing is fabricated.
## Steps
### planning
- component: model + harness/swarm planner
- reads: claude-opus-4-8
- does: would decompose the task into an ordered plan of steps
- confidence: observed
### memory-update
- component: the bound stores (employee.memory.stores)
- reads: episodic, semantic
- confidence: observed
… (8 outros passos — confidence: inferred)
## Inferred
- a connector is a DECLARATION — the connector runtime (A3) is not built,
so whether it actually calls out is not yet wired
- the write policy — hermes memory is currently off-by-default (A3 pending)
## Unknown
- the memory CONTENTS until actually read at runtime
- the scheduler RUNNER (A4) is not built — schedule is a declaration
Note: planning e memory-update são observed porque o modelo primário soul.modelPreferences.primary e os stores bound são fatos da definição. Os outros 8 passos são inferred — a execução ainda não aconteceu quando explain roda, então o motor descreve a intenção, nunca um fato. Os ledgers inferred / unknown consolidam todo caveat de forma deduplicada.
introspect.ts não inventa internos. Quando o connector runtime A3 ainda não existia, o passo tool-use sempre carregava "a connector is a DECLARATION — the connector runtime (A3) is not built". Quando A3 ficou pronto, a opção wiredConnectors passou a derrubar essa caveat apenas para os ids resolvidos — e a frase muda para "would also drive the wired connectors — resolved via the A3 seam, but still engine-inferred (not an observed call)". Com as flags todas off, a saída é byte-idêntica ao comportamento anterior. Cada selo é verificável contra employee.ts ou contra o motor — nunca contra "deveria funcionar".
Rodar uma vez: o dry-run $0
employee run é spend-safe por construção. Offline (default), runEmployeeRunOffline chama buildEmployeeRunInput (puro, sem clock, sem RNG, sem IO) e imprime o system/user exato que seria enviado — nenhum modelo é chamado. Só com --online o ModelAdapter injetado dispara.
$ alembic employee run iris --goal "draft a polite reply to a refund request"
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
- empathy
- precision
## Skills
- issue-triage
- meeting-notes
…
O system é montado por assembleSystemPrompt: começa com renderEmployeePrompt (que lidera com renderSoulPrompt(employee.soul) e anexa ## Skills + ## Connectors só quando não-vazios) e, se houver memoryContext composto, anexa uma seção ## Memory. O requestId é determinístico (employee-{id}), o modelId resolve soul.modelPreferences.primary ?? .fallback ?? opts.fallbackModelId — sem nenhum dos três, err.
O seam de connectors
$ alembic employee connectors iris
employee: iris connectors (offline — no adapters wired)
- gmail: unwired (no adapter)
- slack: unwired (no adapter)
note: real adapters founder-gated
resolveEmployeeConnectors(employee, provider) projeta employee.connectors através de um ConnectorProvider e devolve dois arrays: wired (os ids que resolveram a um ConnectorPort) e unwired (os declarados mas não resolvidos). Offline o provider é offlineConnectorProvider — devolve undefined para todo id, então tudo é honest-unwired. Em testes, createFakeConnectorProvider({gmail: fakePort}) stand-in para os adapters reais. Os 4 verbos seguem o modelo Supercomputer: read / list / write / post. Adapters OAuth reais são founder-gated e moram fora deste seam.
O agendamento → automation
$ alembic employee schedule iris
employee: iris schedule → @alembic/automation manifests
- emp-iris-0 [PAUSED] cron "0 9 * * *" model claude-opus-4-8
task: daily inbox sweep
O schedule[] do A1 é puramente declarativo — cada entrada é { task, cron } com o cron opaco (não é parseado em A1, exatamente como o rrule do subsistema automation). O comando employee schedule mapeia cada entrada para um manifest @alembic/automation via employeeToAutomations (A4) — então o cron daemon existente do automation pode dirigir as reruns sem inventar um scheduler novo. PAUSED é o status default; o founder ativa quando estiver pronto.
O loop de memória: ler e ESCREVER de volta
O passo que faz o employee "lembrar" tem dois lados:
- Leitura (A2, dentro de
runEmployeeTurn): se a opçãoopts.memoryfor dada,composeMemoryTextchamacomposeMemoryContextcom os 5 readers, mas projeta para neutralizar os stores que o employee NÃO binda — substituindo cada não-bound porEMPTY_READER. O composer requer todos os 5 keys, então o binding é respeitado sem mexer no composer. Falha de compose nunca quebra o turno: degrada para''. - Escrita (A3b,
recordEmployeeTurn): depois de um turno bem-sucedido, escreve UM episódio honesto ao storeepisodic— mas somente se o employee bindaepisodic. Oepisodeé o goal verbatim; ocontexté um excerto truncado emWRITEBACK_CONTEXT_MAX(500 chars) doturn.textreal; oatvem doopts.nowinjetado (nuncaDate.now()); oidéwriteback-{id}-{at}(nuncarandomUUID). Nada de "learning" inventado — só o turno real.
Como funciona por dentro
Os arquivos relevantes em packages/hermes/src/employee/:
employee.ts—employeeDefinitionSchema,loadEmployee(injetaSoulReader),listEmployees(injetaEmployeeDirReader),renderEmployeePrompt(puro),resolveMemoryBinding,employeeMemoryIdentity. Todas as funções de leitura nunca lançam — falha =err.run.ts—buildEmployeeRunInput(puro, valida viamodelRunInputSchema) erunEmployeeTurn(best-effort memory + adapter + map paraEmployeeTurnResult). Respeita o invariante: oModelAdapternunca lança, então o turno também nunca.introspect.ts—explainEmployeeExecution(10 step-builders puros) +renderExecutionChainReport(markdown determinístico, byte-stable). CadaStepBuilderrecebe umBuildContextimutável comwiredConnectors,scheduleWired,memoryWriteEnabled— flags off = saída idêntica ao comportamento prévio.connectors.ts—ConnectorPort(4 verbos),ConnectorProvider,resolveEmployeeConnectors(dedupe + first-seen-order, nada é silenciosamente perdido),offlineConnectorProvider,createFakeConnectorProvider.writeback.ts—recordEmployeeTurn+EpisodicAppendPort. Guarda o seam comtryCatchAsync— um port que LANÇA é tratado comoerr, exatamente como um port que retornaerr.memory/multi-store/soul.ts—soulDefinitionSchema+renderSoulPrompt+toMemoryIdentity. O employee embute esse schema verbatim; a identidade flui direto para o composer.
Para entender por dentro: renderEmployeePrompt tem 11 linhas de código real. buildEmployeeRunInput tem ~25 linhas. resolveEmployeeConnectors tem 14 linhas. A camada de composição é fina por desenho — quase tudo é DI sobre o que já existia.
Verifique seu entendimento
Por que o passo memory-update aparece como observed num explain?
Porque quais stores serão escritos é um fato da definição: employee.memory.stores. O write em si é futuro (não aconteceu quando explain roda), então o passo descreve a intenção ("would append"), mas o binding é observável. Sem memory bound, o selo cai para inferred. E enquanto a flag memoryWriteEnabled não estiver ligada, a caveat "off-by-default (A3 pending)" aparece no ledger inferred.
Qual a diferença entre employee run sem flag e employee run --online?
Sem flag: runEmployeeRunOffline chama buildEmployeeRunInput (puro) e imprime o system+user que seria enviado. Nenhum modelo é chamado, custo $0. Com --online: runEmployeeTurnOnline resolve o ModelAdapter real, faz best-effort compose de memória (degradando para vazio se falhar) e chama adapter.run(input). O preview offline existe justamente para você auditar o prompt antes de gastar.
O que muda quando você passa wiredConnectors: ['gmail'] para explainEmployeeExecution?
Em tool-use: a caveat "a connector is a DECLARATION — the connector runtime (A3) is not built" deixa de aparecer no ledger inferred (só seria mantida se houvesse outros connectors não-wired). O does ganha o clause "would also drive the wired connectors (gmail) — resolved via the A3 seam, but still engine-inferred". O selo continua inferred — A3 estar wired não muda que a chamada ainda é futura, não um fato observado. Esse padrão de "byte-stable feature toggle" é o mesmo para scheduleWired (A4) e memoryWriteEnabled (A3b).
Próximas paradas: capabilities (os tijolos $0 que viram skills), swarm-harness (o runtime onde um employee virou unidade de missão), marketing-factory (um employee aplicado a vídeo), use-cases (o caso "contratar + rodar um AI Employee" end-to-end).