Como Wake funciona tecnicamente. Este doc cobre as primitivas, o fluxo de execução, os componentes e os pontos de extensão.
Igual ao Managed Agents:
| Primitiva | O que é |
|---|---|
| Agent | Configuração reusável: modelo + system prompt + tools + MCP servers + skills |
| Environment | Template de container: imagem base + pacotes + network policy |
| Session | Execução de um Agent num Environment, rastreada como state machine |
| Event | Mensagem imutável no log (user message, assistant response, tool call, etc.) |
Esses quatro são os endpoints públicos da API REST.
┌───────────────────────┐
│ CLIENT │
│ CLI / SDK / HTTP │
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ API Server │
│ /v1/agents │
│ /v1/environments │
│ /v1/sessions │
│ /v1/sessions/:id/ │
│ events │
│ stream │
└───────────┬───────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────────┐ ┌────────────────┐
│ Postgres │ │ Event Log │ │ Pub/Sub │
│ (catalog) │ │ (append-only) │ │ (SSE fanout) │
│ agents, │ │ per-session │ │ Redis/NATS │
│ environments │ │ ordered │ │ │
└────────────────┘ └────────────────────┘ └────────────────┘
│
▼
┌───────────────────────┐
│ Harness Worker │
│ stateless │
│ wake/step/emit │
└───────────┬───────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ LLM Provider │ │ Tool Router │ │ Vault + Proxy │
│ via LiteLLM │ │ built-in, │ │ egress filter │
│ Claude/OpenAI/ │ │ MCP, custom │ │ cred injection │
│ local/etc. │ │ │ │ │
└──────────────────┘ └────────┬─────────┘ └──────────────────┘
│
▼
┌────────────────────┐
│ Sandbox Runtime │
│ - sandbox-runtime │
│ - Docker │
│ - Firecracker │
│ - gVisor │
└────────────────────┘
A Anthropic descreve isso em Scaling Managed Agents. Wake adota integralmente.
Tudo num container só: harness + sandbox + state. Simples no diagrama, péssimo na prática:
- Container morre → sessão morre
- Debugar exige shell no container, expõe dados
- Cliente em VPC privada exige peering ou self-host inteiro
Três abstrações separadas:
Session = event log durável (FORA do container)
Harness = loop stateless (FORA do container)
Sandbox = container/runtime (efêmero, é uma tool)
O harness invoca o sandbox por uma interface unificada: execute(name, input) → string. O sandbox pode ser:
- Container Docker
- Sandbox-runtime do anthropic-experimental
- Firecracker microVM
- gVisor
- Máquina remota via SSH
- Outro sistema entirely
O harness não sabe. Não precisa saber.
idle — esperando input (criada, ou após responder)
running — harness ativo
rescheduling — erro transiente, retry automático
terminated — encerrada (sucesso, erro irrecuperável ou interrupt)
1. cliente: POST /v1/sessions
→ session criada em estado `idle`
→ nenhum container provisionado ainda
2. cliente: POST /v1/sessions/:id/events
body: { type: "user.message", content: [...] }
→ evento anexado ao log
→ session passa pra `running`
→ harness worker recebe wake(sessionId)
3. harness:
- getEvents(sessionId)
- construir mensagens pro LLM
- chamar LLM via LiteLLM
- stream da resposta → emitEvent(assistant.delta) × N
- se tool_use:
- resolve tool pelo Tool Router
- se precisa sandbox e não existe → provision()
- executa
- emitEvent(tool_result)
- volta pro step do LLM
- se stop_reason == "end_turn":
- emitEvent(assistant.message final)
- session volta pra `idle`
4. cliente: GET /v1/sessions/:id/stream
→ SSE conectado ao Pub/Sub
→ recebe eventos em tempo real
5. (opcional) cliente: POST /v1/sessions/:id/events
body: { type: "user.message", content: "continue X" }
→ loop reinicia
Sessão criada não significa container existente. Container existe quando uma tool sandboxed é chamada pela primeira vez:
# pseudocódigo no Tool Router
async def execute(tool_name, input, session):
tool = registry.get(tool_name)
if tool.requires_sandbox:
if session.sandbox is None:
session.sandbox = await provision_sandbox(session.environment)
return await session.sandbox.execute(tool_name, input)
else:
# tools sem sandbox (web_search, MCP HTTP, etc.) rodam fora
return await tool.execute(input)Consequência: sessões que só fazem web_search ou conversação nunca ganham container. Custo = zero.
Métrica que isso desbloqueia: TTFT (time-to-first-token) idêntico à API Messages crua, mesmo em sessões "longas." Anthropic reportou redução de 60% no p50 e >90% no p95.
Cenário: harness está no meio de um step. Container provisionado. LLM em streaming. De repente, harness OOM.
T+0 harness worker dies
T+1 watchdog detecta (heartbeat lost)
T+2 novo harness worker pega o lock da sessão
T+3 new_harness.wake(sessionId)
T+4 events = getEvents(sessionId) # tudo até onde tinha sido gravado
T+5 reconstroi contexto
T+6 decide próximo passo:
- se último evento é tool_result completo → continua step do LLM
- se último evento é tool_call sem result → re-executa tool (idempotência)
- se último evento é assistant.delta parcial → re-pede ao LLM
T+7 sessão continua
Crítico: tools precisam ser idempotentes onde possível. Tools com side effects (POST, write_file) precisam de tool_call_id para deduplificação no nível do Tool Router.
Tools são primeira classe. Toda tool implementa:
class Tool(Protocol):
name: str
schema: JSONSchema # spec do input
requires_sandbox: bool
permission: PermissionPolicy
async def execute(self, input: dict, ctx: ToolContext) -> ToolResult: ...Origens de tools:
| Tipo | Origem | Roda onde |
|---|---|---|
| Built-in | Wake | Sandbox (bash, file_ops) ou host (web_search) |
| MCP stdio | mcp_servers no Agent |
Inside sandbox, communication via stdio |
| MCP HTTP | mcp_servers no Agent |
Externamente, via agentgateway |
| Custom | Definida pelo usuário | Host por default, opt-in sandbox |
O agentgateway (Linux Foundation) faz a ponte para MCP HTTP/SSE com autenticação via vault.
O harness nunca toca em credencial. O fluxo é:
1. user cria vault entry:
wake vault add github_token --provider github --oauth
→ OAuth flow no browser
→ token armazenado no Infisical Agent Vault
2. user cria sessão referenciando vault:
wake session create --agent X --vault github_token
3. harness chama tool autenticada:
await tools.execute("github.create_pr", {...})
4. tool resolve via egress proxy:
- proxy recebe request com placeholder no Authorization header
- proxy busca token real no vault
- proxy injeta no header
- proxy faz a chamada externa
5. response volta pelo proxy:
- tokens removidos de qualquer eco (defensivo)
- response retornada ao tool
- tool retorna ao harness
O harness emite o evento tool_result sem nunca ter visto o token. Mesmo se o log for vazado, não há credencial nele.
Cada sessão tem um event log próprio. Append-only. Eventos têm:
type Event = {
id: string; // ULID, monotonic
session_id: string;
seq: number; // posição na sessão (0, 1, 2, ...)
type: EventType;
payload: object; // depende do type
created_at: string; // ISO 8601
parent_id?: string; // para hierarquia (tool_result aponta pra tool_use)
}Storage: Postgres (Day-1), com flag para alternativas (SQLite local, Kafka, S3 + index).
Garantias:
- Ordenação total por sessão (não global)
- Imutabilidade (eventos não atualizam, apenas append)
- Durabilidade (fsync antes de ACK ao harness)
- Idempotência via tool_call_id (dedupe se harness re-emite)
Schema completo em SPEC-EVENT-SCHEMA.md.
Tudo descrito até aqui assume um harness Wake-nativo. Mas o ponto da arquitetura é que qualquer harness conforme a interface roda.
class HarnessAdapter(Protocol):
async def step(
self,
ctx: SessionContext,
events: EventStream,
tools: ToolRegistry,
) -> AsyncIterator[Event]:
"""
Roda um step da sessão.
Recebe: contexto da sessão, stream de eventos (já gravados),
registro de tools disponíveis.
Retorna: stream de novos eventos a serem gravados.
Pode encerrar a qualquer momento; será chamado novamente.
"""
...Adapters de referência:
wake.adapters.claude_sdk— wrapper sobreanthropic.Anthropicdiretowake.adapters.langgraph— wrapper sobrelanggraph.graph.StateGraphwake.adapters.crewai— wrapper sobrecrewai.Crewwake.adapters.pydantic_ai— wrapper sobrepydantic_ai.Agent
Spec completa em SPEC-HARNESS-ADAPTER.md.
Replay determinístico:
wake session replay session_xyz \
--from-event 47 \
--to-fork-as session_debug001Como funciona:
- Lê events 0..47 do source
- Cria nova session com os mesmos events
- Reprovisiona container (lazy)
- Chama harness com o mesmo HarnessAdapter, mesmo modelo, mesma seed (se snapshotada)
- A partir do event 47, harness segue normalmente — mas é uma nova trilha
LLM amostragem é estocástica. Para determinismo total:
- Wake pode (opcionalmente) snapshotar a resposta original e replayar — útil pra debug puro
- Ou pode pedir nova amostragem — útil pra explorar alternativas
CLI:
wake session replay XYZ --from 47 --use-snapshots # determinístico
wake session replay XYZ --from 47 --resample # nova amostragemWake suporta múltiplos backends de sandbox via adapter:
class SandboxAdapter(Protocol):
async def provision(self, env: Environment) -> SandboxHandle: ...
async def execute(self, handle: SandboxHandle, tool: str, input: dict) -> ToolResult: ...
async def destroy(self, handle: SandboxHandle) -> None: ...Backends planejados:
wake.sandbox.docker— Day-1, Docker simples (baixa segurança, alta compat)wake.sandbox.sandbox_runtime— sandbox-runtime do anthropic-experimental (bubblewrap+seccomp+proxy)wake.sandbox.firecracker— microVMs (futuro, alta segurança)wake.sandbox.gvisor— kernel userspace (futuro, alta segurança)
User escolhe via Environment config:
environment:
name: my-env
sandbox:
backend: sandbox-runtime # default; alternativas: docker, firecracker, gvisor
network:
mode: limited
allowed_hosts: [github.com, pypi.org]Wake adiciona uma camada RBAC fina por cima da fronteira de tenancy.
Três roles fixas — admin, operator, viewer — bound a
(user_id, workspace_id). Gates ficam na assinatura das rotas via
Depends(require_role(...)); handlers nunca fazem inline-check.
Habilitação via env: WAKE_RBAC_ENABLED=true. Default é false
(zero-friction dev mode — get_current_user devolve o sentinel
User.system() carregando todas as roles, e nenhuma rota é gateada).
| Role | Escopo |
|---|---|
| admin | CRUD completo + gerenciar users + rotacionar credenciais |
| operator | CRUD em agents/environments/sessions/events; lê vault audit |
| viewer | Read-only (sessions, events, métricas, agents) |
Identidade entra via X-Wake-User-Id (gateway/IdP injeta após auth
própria). Cross-workspace continua sendo 404 (opacidade da
tenancy); cross-RBAC sem permissão é 403. Doc completa em
RBAC.md.
Workspace = fronteira de isolamento de dados. organization_id agrupa
workspaces; workspace_id escopa agents, environments, sessions, events,
SSE, replay e métricas. Adaptadores de produto podem mapear customer,
projeto, conta ou tenant para esses campos sem depender de metadata.
Sessão = unidade de execução dentro do workspace. Cada sessão tem:
- Event log próprio (por session_id)
- Container próprio (se provisionado)
- Vault scope próprio
- Permission policy própria
Múltiplos harness workers podem rodar concorrente, cada um pegando um session_id por vez via lock advisory (Postgres pg_try_advisory_lock).
Para escala alta: harness workers em FaaS / Kubernetes Jobs / qualquer scheduler.
Wake não embute observability. Mas garante que o event log é um stream OpenTelemetry-compatível:
- Cada event emite um span
- Tool calls têm child spans
- LLM calls têm input_tokens / output_tokens / cost
- Erros têm exception attributes
Consumidores plugáveis:
- Langfuse, Phoenix, Helicone, Braintrust — consomem via OTel
- LangSmith — via adapter custom
- Datadog/NewRelic — via OTel collector
A API REST de Wake tem endpoints idênticos onde semanticamente possível:
| Endpoint Managed Agents | Endpoint Wake |
|---|---|
POST /v1/agents |
POST /v1/agents |
POST /v1/environments |
POST /v1/environments |
POST /v1/sessions |
POST /v1/sessions |
POST /v1/sessions/:id/events |
POST /v1/sessions/:id/events |
GET /v1/sessions/:id/stream |
GET /v1/sessions/:id/stream |
Beta header managed-agents-2026-04-01 é aceito mas não exigido (compatibilidade superficial; Wake é GA-stable na sua própria versão).
Diferenças deliberadas (documentadas em COMPARISON.md):
- Wake suporta
harness_adapterna criação de Agent (Managed Agents só Claude) - Wake suporta múltiplos backends de sandbox
- Wake é self-host
- API Server (REST + SSE) — Postgres + harness workers
- Event log — store apenas Wake, schema canônico
- Tool Router — interface unificada para tools
- HarnessAdapter ABI — spec aberta + adapters de referência
- CLI + SDKs — UX para dev local e cliente programático
Tudo o resto pluga: sandbox-runtime, Infisical Vault, LiteLLM, MCP, agentgateway.