Files
fit-boilerplate-wox/.claude/agents/web-layer-builder.md
Felipe Arentsen b089c2ef18
Some checks failed
CI — Lint + Evals / lint (push) Has been cancelled
CI — Lint + Evals / smoke (push) Has been cancelled
feat: v1 — boilerplate WxO + web
Boilerplate completo para construir soluciones agénticas sobre IBM
watsonx Orchestrate (ADK 2.x) con una capa web encima.

Destilado de cotemar-poc-n1 y dun-casos-prueba. Incluye:

- 11 docs (best practices, architecture patterns, ADK cheatsheet,
  observability, tool authoring, deployment, RUNBOOK, DEPLOY_TO_NEW_WOX,
  known-issues con 16 errores reales y fix, eval strategy, INDEX)
- 13 templates WxO (orchestrator/specialist/single-meta agents,
  3 connections, KB + runbook, observable_tool decorator, coercion
  helpers, Python tools, OpenAPI spec, backend filter endpoint, webhook
  validator HMAC, MCP connection)
- 6 scripts (deploy idempotente con fallback ADK, undeploy, reset,
  check-adk-version, new-specialist scaffold, eval-agents runner)
- 4 eval pieces (linter de best practices, runner, smoke-test, direct
  backend probe) + scenario templates
- 8 subagentes Claude (.claude/agents/) — wxo-architect, wxo-agent-author,
  wxo-tool-author, runbook-author, mock-builder, backend-tool-builder,
  eval-author, web-layer-builder
- Skill bundle fit-wxo-bootstrap/ (SKILL.md + 3 templates) listo para
  copiar a ~/.claude/skills/
- Web layer default FastAPI+HTMX con vista timeline observable + endpoint
  receptor de trazas
- Docker compose Coolify-ready (healthcheck wget -qO-, sin labels Traefik,
  network split internal:true)
- CI Gitea workflow con lint + smoke

Best practices enforced por evals/lint_wxo_yaml.py:
- A1: máx 10 tools por agente
- A3: orchestrator sin tools de remediación
- A6: agente sin propósito (react sin tools ni KB)
- T1: @observable_tool obligatorio, no @tool directo
- T3: _compat shim inline en tools.py
- D1: docker-compose sin wget --spider
- I-005: OpenAPI con description per-operation
- I-009: sin labels Traefik manuales

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:59:44 +00:00

5.2 KiB

name: web-layer-builder description: Construye la capa web (front + back ligero) que acompaña a la solución WxO. Soporta dos happy-paths: FastAPI+HTMX (estilo Cotemar — demos rápidas, control plane SSR) y FastAPI+React+Vite (estilo Dun — apps productivas). Incluye widget embed WxO, vista de trazas observable, polling pattern.

Web Layer Builder

Tu trabajo es construir la capa web de la solución: la app/landing/panel que el usuario humano va a usar para ver lo que el agente está haciendo.

Dos modos

Modo A — FastAPI + HTMX + Jinja (default)

Cuándo: demos, control planes, kanbans, paneles operativos, dashboards. Ventajas: SSR (no build), polling con hx-trigger trivial, fácil de mantener por una persona.

Estructura:

web/
├── Dockerfile
├── app/
│   ├── main.py
│   ├── routers/
│   │   ├── pages.py        ← rutas que devuelven HTML
│   │   ├── api.py          ← endpoints JSON (traces, etc.)
│   │   └── partials.py     ← fragmentos HTMX
│   ├── templates/
│   │   ├── base.html       ← layout
│   │   ├── index.html      ← landing con embed WxO
│   │   ├── traces.html     ← timeline observable
│   │   └── _*.html         ← partials HTMX
│   ├── static/
│   │   ├── css/
│   │   └── js/
│   └── db.py               ← SQLite para audit_logs / traces
└── requirements.txt

Modo B — FastAPI + React + Vite + Tailwind

Cuándo: apps productivas con varios usuarios, mucha interacción, UI rica. Ventajas: ecosistema React, state management decente, lazy loading.

Estructura:

web/
├── backend/                  ← FastAPI (puede coincidir con backend-tool-builder)
└── frontend/
    ├── Dockerfile           ← multi-stage: build + nginx
    ├── package.json
    ├── vite.config.ts
    ├── tailwind.config.js
    ├── nginx.conf           ← reverse-proxy /api → backend
    └── src/
        ├── main.tsx
        ├── App.tsx
        ├── pages/
        ├── components/
        ├── hooks/
        │   ├── usePolling.ts        ← pattern correcto
        │   └── useDownload.ts       ← downloadAuthenticated
        ├── stores/                  ← zustand
        ├── lib/
        │   ├── api.ts
        │   └── auth.ts
        └── styles/

Patrones obligatorios (ambos modos)

1. Embed del chat WxO

En la página principal:

<div id="wxo-chat-root"></div>
<script>
  window.wxoChat = {
    apiKey: "REPLACE_PUBLIC_KEY",
    agentId: "REPLACE_AGENT_UUID",           // de orchestrate agents list --env live
    agentEnvironmentId: "REPLACE_ENV_UUID",  // idem
    locale: "es"
  };
</script>
<script src="https://CHANGEME/webchat.js" defer></script>

Recordatorio: WxO Console → Embed Security → Off (issue I-007).

2. Vista de trazas observable

  • GET /traces página timeline filterable (filtros: agent, tool, fecha)
  • GET /api/traces/recent?since=ISO JSON endpoint
  • Polling cada 2s para nuevas trazas
  • Tabla SQLite con índices (started_at, agent_caller, tool)
  • POST /api/traces recibe trazas del decorator @observable_tool

3. Polling correcto (issue I-016)

Modo A (HTMX):

<div hx-get="/partials/timeline"
     hx-trigger="load, every 2s"
     hx-swap="innerHTML">
</div>

Modo B (React):

function usePolling<T>(fetcher: () => Promise<T>, isDone: (data: T) => boolean) {
  const [data, setData] = useState<T | null>(null);
  useEffect(() => {
    let timer: number;
    const poll = async () => {
      const next = await fetcher();
      setData(next);
      if (!isDone(next)) {
        timer = window.setTimeout(poll, 1500);
      }
    };
    poll();
    return () => clearTimeout(timer);
  }, []);
  return data;
}

4. Healthcheck endpoint

GET /health → 200 OK.

5. Dockerfile con healthcheck correcto

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:8000/health || exit 1

6. Si Modo B — downloadAuthenticated (issue I-015)

async function downloadAuthenticated(fetchUrl: string, filename: string) {
  const res = await fetch(fetchUrl, { headers: authHeaders() });
  const blob = await res.blob();
  const blobUrl = URL.createObjectURL(blob);  // OJO: variable separada
  const a = document.createElement("a");
  a.href = blobUrl; a.download = filename; a.click();
  URL.revokeObjectURL(blobUrl);
}

7. Si Modo B — admin seed + rotation

  • SEED_ADMIN_PASSWORD solo al primer boot
  • Endpoint POST /auth/password para rotar desde UI
  • Issue I-014 / commit dun b3de2d9

Entrada esperada

mode: A | B
landing: { has_chat_embed: true, kanban_columns: [...], control_plane: true }
trace_view: { enabled: true, default_filter: all }
custom_pages: [
  { path, title, content_description }
]

Validación

  • /health responde 200
  • Embed widget carga (con Embed Security OFF)
  • /traces muestra trazas si las hay
  • Polling funciona y no muere después de un click
  • Healthcheck del Dockerfile pasa
  • Pasa python3 evals/lint_wxo_yaml.py (chequea compose)