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

AI Employee composição (A1) soul.ts identidade skill-store skills[] multi-store/* 5 substores connectors (A3) read/list/write/post schedule (A4) cron opaco → automation

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:

1. msg→task 2. context-asm 3. planning 4. tool-use 5. verify 6. output 7. mem-update 8. report 9. next-loop 10. recover selos: observed = lido da definição inferred = comportamento default do motor unknown = interno não inspecionável (ex: o CONTEÚDO dos stores até serem lidos)
$ 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.

Anti-fabricação ESTRUTURAL. 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:

Como funciona por dentro

Os arquivos relevantes em packages/hermes/src/employee/:

  • employee.tsemployeeDefinitionSchema, loadEmployee (injeta SoulReader), listEmployees (injeta EmployeeDirReader), renderEmployeePrompt (puro), resolveMemoryBinding, employeeMemoryIdentity. Todas as funções de leitura nunca lançam — falha = err.
  • run.tsbuildEmployeeRunInput (puro, valida via modelRunInputSchema) e runEmployeeTurn (best-effort memory + adapter + map para EmployeeTurnResult). Respeita o invariante: o ModelAdapter nunca lança, então o turno também nunca.
  • introspect.tsexplainEmployeeExecution (10 step-builders puros) + renderExecutionChainReport (markdown determinístico, byte-stable). Cada StepBuilder recebe um BuildContext imutável com wiredConnectors, scheduleWired, memoryWriteEnabled — flags off = saída idêntica ao comportamento prévio.
  • connectors.tsConnectorPort (4 verbos), ConnectorProvider, resolveEmployeeConnectors (dedupe + first-seen-order, nada é silenciosamente perdido), offlineConnectorProvider, createFakeConnectorProvider.
  • writeback.tsrecordEmployeeTurn + EpisodicAppendPort. Guarda o seam com tryCatchAsync — um port que LANÇA é tratado como err, exatamente como um port que retorna err.
  • memory/multi-store/soul.tssoulDefinitionSchema + 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).