▲ ┌───────────────┐ ┌──────────┐ ┌───────────┐ │ │ 27 NODES │ → │ 11 LAYERS │ → │ ∞ AGENTS │ ▼ └───────────────┘ └──────────┘ └───────────┘
Technical Blueprint · AI Engine v3

Um engine.
N agents.
Zero código por cliente.

A v1 foi um agente em TypeScript com prompt acoplado a stages fixas. A v3 é um engine genérico que carrega Agent Blueprints do banco, orquestra 27 nodes em 11 layers (com paralelismo real) e executa Agent Operating Procedures versionados — sem recompilar. Tudo observável, tudo testável, tudo reversível.

Nodes
27 / 11 layers
Paralelismo
~60% dos nodes
Custo alvo
-78% tokens
Ship
Q3 2026
01 Pipeline

27 nodes, 11 layers,
paralelismo explícito.

Cada node é uma função pura, tipada, testável isoladamente. Nodes dentro da mesma layer rodam em paralelo via LangGraph fan-out. Judges isolados depois de cada decisão cara — não no meio dela.

▸ engine.pipeline.flow
layers: 11 · nodes: 27 · parallel: 16
L01 · intake
rateLimitGuard spamDetection banCheck debounce
║ parallel
L02 · context
memoryLoader blueprintLoader variablesHydrator
║ parallel
L03 · analyze
conversationAnalyzer analyzerJudge
→ serial
L04 · route
stageRouter resistanceDetector handoffSignal
║ parallel
L05 · retrieve
knowledgeRetriever mediaRetriever socialProofRetriever
║ parallel
L06 · plan
toolPlanner stagePlanner
║ parallel
L07 · execute
toolExecutor toolResultJudge
→ serial
L08 · respond
responseGenerator
→ serial
L09 · judge
responseJudge safetyJudge
║ parallel
L10 · commit
stageTransitioner memoryWriter traceEmitter
║ parallel
L11 · terminal
winFinalizer lossFinalizer handoffFinalizer
conditional
standard parallel fan-out critical guard llm-as-judge terminal
// anatomy of a nodecontract
function createResponseGeneratorNode(ctx)
factory · pure
Input
ChatState (immutable) — reads only what it declared in its reads[] contract
Output
Partial<ChatState>writes only what it declared in its writes[] contract. Engine fails loud on undeclared writes.
Deps
NodeContext injected — ai, logger, clock, vectorStore, retry, metrics. Zero third-party imports inside the node.
Tracing
Span opened automatically on entry, closed on return. Errors tagged. Zero boilerplate.
Testing
buildMockChatState() + mock providers. Each node has a .test.ts next to it. No integration required.
02 Agent Blueprint

Agente vira dados,
não mais código.

Hoje: cada agent version é uma pasta em libs/ai-engine/src/agents/, com agent.ts, stages.ts, prompts/ — recompile para alterar qualquer coisa. v3: um Agent Blueprint no banco, declarativo, versionado, fork-safe, com templates parseados em runtime.

agent-blueprint.schema.ts
// Cada AgentVersion carrega-se do DB; engine é 100% genérico.
type AgentBlueprint = {
  id:              BlueprintId;           // abp_... — immutable, fork para editar
  agentVersionCode: string;              // "magrass-sdr@v2.3.1"
  organizationId?: OrganizationId;       // null → template global

  persona:         PersonaAOP;             // nome, tom, gênero, linguagem
  stages:          StageAOP[];             // discriminated por kind
  variables:       VariableSchemaAOP;      // zod-compiled schema
  tools:           ToolBindingAOP[];       // aliases + whenToCall + howToCall
  validators:      ValidatorAOP[];         // llm-judges + code-checks
  memory:          MemoryPolicyAOP;        // scope, retention, compaction
  templates:       TemplateRefAOP[];       // liquid templates versionados
  rollout:         RolloutAOP;             // canary %, auto-rollback thresholds
};

// engine.ts — sem conhecimento de agente específico
const blueprint = await blueprintRepo.loadByCode('magrass-sdr@v2.3.1');
const result = await engine.run({ blueprint, conversation, inbound });
// discriminated stage kinds11 kinds
collect
COLLECT_VARIABLES
Pergunta até ter required[] preenchidas. Retries com clarification.
catalog
SELECT_FROM_CATALOG
Apresenta lista N, detecta escolha semântica, valida contra catálogo.
tool
TOOL_EXECUTE
Chama tool + pre/post validators + side-effects declarativos.
payment
PAYMENT_GATE
Espera webhook externo. ACTIVE, não AWAITING_*. Timeout configurável.
handoff
EXTERNAL_HANDOFF
Transfere para humano via tool de integração. Status → HUMAN_TAKEOVER.
branch
BRANCHING
Decide próximo stage via LLM-judge + regras determinísticas.
open
OPEN_CHAT
Sem gate — para FAQ, support, small-talk. Knowledge-heavy.
terminal
TERMINAL_WIN · LOSS · HANDOFF · EXPIRED
4 terminais distintos. Cada um emite métrica e congela a conversa.
hitl
HUMAN_IN_LOOP
Pausa para aprovação humana antes de ação cara. Snapshot + resume.
03 Status Model

Status é 3D. Stages são lineares.
Humano tem autoridade final.

Uma conversa tem três dimensões ortogonais de estado: quem controla, qual o resultado, quem classificou o resultado. Isso elimina a confusão AWAITING_PAYMENT vs. TRANSFERRED vs. ACTIVE — essas viram stages, não status.

dimensão valores semântica
currently_controlled_by SYSTEM HUMAN NOBODY Quem está respondendo agora. HUMAN trava a IA automaticamente.
outcome PENDING WIN LOSS HANDOFF EXPIRED Resultado terminal. PENDING = conversa viva. Terminais emitem métrica.
outcome_classified_by SYSTEM HUMAN Quem definiu. Humano sempre pode reclassificar retroativamente.
// casos canônicos6 estados
idle
conversa viva, IA no comando
SYSTEM · PENDING · SYSTEM
takeover
humano assumiu manualmente
HUMAN · PENDING · SYSTEM — IA silenciada até release.
ai-won
conversão automática
SYSTEM · WIN · SYSTEM — stage terminal disparou.
human-won
humano fechou via takeover
HUMAN · WIN · HUMAN — revenue atribuído ao humano.
reclassified
IA errou; humano corrigiu
SYSTEM · LOSS · HUMAN — gera training signal.
expired
inatividade sem follow-up
NOBODY · EXPIRED · SYSTEM — engine background-job fechou.
04 Templates

Templates são first-class,
versionados, fork-safe.

Todo pedaço de texto que vai para o LLM — system prompt, few-shot exemplo, mensagem de fallback — vive como Template no banco, com ID, versão e parse tree. Sem override parcial. Quer editar? Fork. Cada blueprint isolado, zero herança cruzada.

template.liquid
{# tpl_response_gen @v4 — owned by abp_magrass_sdr_v2 #}
{# fork from: tpl_response_gen@v3 (global) #}

<role>
  Você é {{ persona.name }}, {{ persona.role }} da {{ org.name }}.
</role>

<context>
  {% for variable in required_variables %}
    {{ variable.name }}: {{ variables[variable.name] | default: "[ausente]" }}
  {% endfor %}
</context>

<examples>
  {% render 'few-shot' for stage.few_shot_examples %}
</examples>

<output>JSON: { response, reasoning, confidence }</output>
// template lifecycleimmutable · versioned · indexed
tpl@v1
FORK
tpl@v2 (abp_xyz)
parse · validate · index
canary 5%
↓ judge threshold breach
auto-rollback
tpl@v1 restaurado
alert · diff · reason
✗ rejeitado
override granular:
tpl_response_gen.greeting = "Oi!"
tpl_response_gen.closing = "..."
// diverge silenciosamente, impossível rastrear
✓ aceito
fork + edit completo:
fork(tpl_response_gen@v3) → tpl_custom@v1
blueprint.templates.replace(...)
// lineage preservado, diff auditável
05 Tools

Tool alias, não provider.tool_name.
Impact analysis automático.

Blueprint chama scheduling.search. Engine resolve para calendly.find_slots OU google.freebusy conforme org. Trocar provider sem recompilar, sem editar prompt. Plus: AOP declara whenToCall + howToCall + afterCall — LLM sabe exatamente o que fazer.

scheduling.stage.aop.yml
tools:
  - alias: scheduling.search          # semantic, not provider-specific
    required: true
    frequency: AT_LEAST_ONCE

    whenToCall: |
      Quando ainda não foram apresentados horários OU cliente pediu novas opções
      OU horários anteriores foram rejeitados.

    howToCall:
      arguments:
        targetDate:
          source: llm
          instruction: |
            Se cliente disse "amanhã" → runtime.tomorrowDate.
            Se "semana que vem" → próxima segunda.
            Default → next business day.
        clientPhone:
          source: state.lead.phoneNumber    # engine fills, LLM can't hallucinate

    afterCall:
      onSuccess:
        instruction: "Sugira o slot mais próximo proativamente."
        sideEffects:
          - setVariable: { lastSearchAt: "${runtime.now}" }
      onFailure:
        instruction: "Diga que teve instabilidade e vai retornar em minutos."
        retry: { maxAttempts: 2 }
// impact analysisreference_index
before · v1
Mudar tool = medo
Renomear schedulingSearch quebra 4 prompts hardcoded, 2 testes e o judge — e você só descobre em produção.
after · v3
Mudar tool = query
reference_index.where(ref: 'scheduling.search') → lista todos os blueprints, stages, templates, validators que dependem. CI bloqueia merge se houver referência órfã.
06 Memory

Memória em 4 escopos,
sem vazamento entre tenants.

A v1 joga tudo no ChatState e reza. v3 separa memória em quatro escopos ortogonais com políticas de retenção e compactação independentes — cada um com RLS no Postgres e TTL distinto.

scope contém retenção compactação isolamento
conversation mensagens, variáveis, stage atual ativa + 90d sliding window + summary por conversationId
lead preferências, histórico, sentimentos lifetime fato semântico destilado por leadId
organization objeções conhecidas, clusters lifetime embedding-clustered por organizationId (RLS)
agent aprendizados cross-org (templates) lifetime curadoria manual por agentVersionCode
// group conversationsshared context, not multithreading
human A
human B
human C
1 conversation
primary_lead_id
addressing strategy: @mention · last-speaker · broadcast
07 HITL + FITL

Humano é árbitro,
não espectador.

HITL (Human-in-the-Loop): pausa síncrona em stage crítico, com snapshot + resume. FITL (Feedback-in-the-Loop): humano corrige resposta da IA retroativamente, e o delta vira sinal de treino — meta-AI agrega para proposta de ajuste de template.

stage.payment_gate
HITL pause
snapshot ChatState
human approves
resume from snapshot
↓ if human edits draft
diff(ai_draft, human_final)
FITL signal
meta-AI aggregator
template fork proposal
engineer reviews
// live eval + auto-rollbackblueprint-version level
threshold
Admin-configurável
Win rate, judge pass rate, latency p99, cost per conv. Definido por org, por agent version.
canary
Shadow + 5% → 100%
Nova versão entra em shadow mode (compara mas não publica). Passou 24h → 5% real → 25% → 100%.
rollback
Auto @ blueprint-version
Breach em qualquer threshold → engine troca activeVersion da org. Alert + diff + reason. Zero humano.
08 Tracing & Observability

Tudo tracked,
nada hardcoded.

Tracing via Dependency Injection — igual ao MetricsService e ClockService já fazem. Engine não conhece Langfuse; só conhece TracerProvider. Trocar vendor = um módulo em externals/, zero mudança em core.

engine/providers/tracer.provider.ts
export abstract class TracerProvider {
  abstract startSpan(params: StartSpanParams): Span;
  abstract addEvent(span: Span, event: TraceEvent): void;
  abstract setAttribute(span: Span, key: string, value: unknown): void;
  abstract endSpan(span: Span, status: SpanStatus): void;
}

// externals/observability/langfuse/langfuse-tracer.service.ts
// ← ONLY file allowed to import 'langfuse' SDK

// swap vendor → create externals/observability/arize/ and re-register
// what we emit5 spans · N events
span
conversation.turn
Root. Dura da entrada da msg até resposta publicada.
span
pipeline.layer
1 por layer. Mede paralelismo real vs teórico.
span
pipeline.node
1 por node. Input/output size, duration, cost.
span
ai.call
Model, tokens in/out, cached/uncached, temperature, stop reason.
span
tool.execute
Tool alias + concrete provider, args hash, result status.
event
judge.verdict
Rubric, verdict, reasoning, escalation trigger.
event
stage.transition
From / to / reason / actor (SYSTEM or HUMAN).
event
circuit.trip
Tool or model falhando — engine abre breaker, rota fallback.
09 Stack Decisions

Cada escolha,
uma razão.

Zero hype. Cada peça escolhida por um motivo específico, com alternativa testada e descartada.

orquestração
LangGraph
State reducers + fan-out nativo. Já em prod. Alt: Mastra (menos maduro no Node).
templates
Liquid
Sandboxed, sem exec arbitrário, parse tree inspecionável. Alt: Handlebars (menos seguro).
modelo primário
Gemini 3 Flash
Context caching agressivo (-78% token cost). 1M context. Alt: Claude Haiku (sem caching).
fallback model
GPT-5 mini
Circuit breaker auto-routes quando Gemini falha. Provider-agnostic via AiProvider.
state store
Postgres + RLS
ChatState snapshots, blueprints, templates, references. Row-Level Security por org.
cache / queue
Redis + BullMQ
Debounce, rate-limit, follow-up jobs, HITL suspend/resume. Já em prod.
vector
pgvector
KB search, memory recall. No extra infra. Alt: Pinecone (caro + latência).
observability
Langfuse + Sentry
Langfuse p/ traces de LLM. Sentry p/ errors/metrics. Ambos via DI.
eval
ai-engine-eval
Biblioteca própria: simulator + evaluator + TestSuite. Export/import prod ↔ dev.
10 Roadmap

Big bang,
em 4 fases.

Sem migração gradual. v1 e v3 coexistem em runtime; orgs migram por feature flag. Quando 100% migrado, v1 é deletado inteiro.

Q2 2026 · Fase 0 · Foundation
Engine core + Blueprint schema
AgentBlueprint carregado do DB. Stage kinds discriminados. Template parser (Liquid + validate). TracerProvider abstract + Langfuse concrete. ChatState immutable + reducer-pure. 5 nodes iniciais (intake + analyze + route + respond + judge).
blueprintliquidtracer-di5 nodes
Q3 2026 · Fase 1 · Parity
MAGRASS SDR no engine v3
Todos os 27 nodes em produção. Um único blueprint (MAGRASS SDR v2) rodando em v3, paralelo à v1 em shadow. Canary 5% → 25% → 100% durante 3 semanas. Métricas: win-rate paridade ±2%, latency p99 -30%, cost -60%.
27 nodescanaryshadow-eval
Q4 2026 · Fase 2 · Multi-tenant
CEC blueprint + fork flow
Segundo blueprint (CEC) forkando templates da MAGRASS onde aplicável. Admin UI para editar templates + canary dela. Circuit breakers p/ tool + modelo. FITL signal aggregator em sombra.
cecadmin-uicircuit-breakersfitl
Q1 2027 · Fase 3 · Platform
SDK público + group conversations
SDK TypeScript para clientes enterprise (self-host blueprint). Group conversations GA. HITL UI polished. FITL meta-AI emitindo propostas reais de template fork. Auto-rollback em produção.
sdkgroup-convhitl-uiauto-rollback
Q2 2027 · Fase 4 · Shutdown
v1 deletado
100% das orgs em v3. Agent-engine v1 (libs/ai-engine/src/agents/magrass-sdr/) deletado. CI bloqueia qualquer PR que adicione código de agente específico fora de blueprint. Linha final.
cleanupci-ruleone-engine
11 Anti-patterns

O que não vamos fazer,
e por quê.

Cada item aqui foi debatido, testado, ou vivido em produção. Registro para não voltarmos a cair.

✗ regex em linguagem natural
Detectar "adorei", jailbreak, confusão via regex é frágil. Qualquer paráfrase quebra. → LLM judge sempre.
✗ silent fallback em campo crítico
cost ?? 0, agentVersion ?? 'unknown' escondem bugs por dias. → throw, sempre.
✗ override granular de template
Mudar 1 frase por cliente cria explosão combinatória impossível de auditar. → fork completo.
✗ third-party em core
import langfuse dentro de node bloqueia swap. → sempre via Provider abstract + externals/.
✗ código por cliente
Branch/flag para "customer X needs this" é dívida técnica imediata. → virou blueprint ou não entrou.
✗ fix de alucinação com código
Regex de "não pode dizer X" perde. → fix semântico no prompt, sempre.
✗ rollback granular de prompt
Versionar cada template isolado cria estado inconsistente entre templates do mesmo blueprint. → rollback no nível blueprint version.
✗ feedback direto do cliente-final
Somos B2B2C. Cliente-final não distingue IA da clínica. → só FITL do operador humano.
ship Q3 · 2026

A plataforma é o produto.
Cada agent é um dado.

Um engine, N agents, zero código por cliente. Tudo declarativo, tudo versionado, tudo reversível. Humano tem autoridade. IA tem velocidade. Engine tem disciplina.

engineering · lead2go · v3.0.0-rfc