Ir al contenido

Agentes

wu.ai.agent(goal) le da un objetivo al modelo y lo deja trabajar. Es un generador asíncrono: cada paso se emite, así que puedes renderizarlo, registrarlo o parar el bucle saliendo del for.

for await (const step of wu.ai.agent('Find all orders above $100 and summarize them')) {
console.log(`Step ${step.step} [${step.type}]:`, step.content?.slice(0, 100));
}
function agent(goal: string, options?: {
maxSteps?: number; // 10 por defecto
provider?: string;
namespace?: string; // 'agent:…' autogenerado si se omite
systemPrompt?: string | ((goal: string) => string | Promise<string>);
onStep?: (step: AgentStepResult) => void | Promise<void>;
shouldContinue?: (step: AgentStepResult) => boolean | Promise<boolean>;
signal?: AbortSignal;
temperature?: number;
maxTokens?: number;
}): AsyncGenerator<AgentStepResult>;
interface AgentStepResult {
step: number; // empieza en 1
type: 'thinking' | 'tool_call' | 'done' | 'blocked' | 'aborted' | 'interrupted';
content: string | null;
toolResults: Array<object> | null;
usage: object | null;
reason: string | null;
elapsed: number; // ms
}

El paso 1 envía el objetivo. Cada paso posterior envía literalmente el prompt “Continue working toward the goal. If you are done, include [DONE] in your response.” Cada paso es un conversation.send() completo, así que el bucle de tool calling corre dentro de un único paso del agente: un paso puede implicar varias rondas de tools.

Cada paso se trata como una llamada de nivel superior a depth: 0 con un trace ID nuevo. maxSteps gobierna el presupuesto de pasos; maxDepth gobierna la recursión causal, y son independientes.

Se comprueba en este orden:

# Condición type emitido
1 Se disparó la señal de abort aborted
2 La comprobación previa de permisos denegó (límite de peticiones, breaker, guardia de bucles) blocked
3 La respuesta contiene la cadena literal [DONE] done
4 El modelo llamó a tools en un paso anterior y no llamó a ninguna en este done
5 shouldContinue(step) devolvió false (o lanzó) interrupted
6 Se alcanzó maxSteps done, reason: 'Max steps (N) reached'

Cuando encuentra el marcador, wu lo elimina del content emitido.

for await (const step of wu.ai.agent('Reorganize the product catalog', {
maxSteps: 6,
onStep: (s) => renderStep(s),
shouldContinue: async (s) => await askUser(`Continue after step ${s.step}?`),
})) {
if (step.type === 'blocked') showLimitWarning(step.reason);
}

onStep se ejecuta antes de emitir el paso y sus errores se tragan: un renderizador roto nunca rompe el bucle. shouldContinue se ejecuta después de emitirlo; si lanza, cuenta como “parar”.

Salvo que lo sobrescribas, wu genera uno que contiene: el protocolo del agente (incluido el contrato [DONE]), las apps montadas, la instantánea actual del store, una firma de una línea por cada tool disponible y, al final, GOAL: <goal>.

Los namespaces agent:* autogenerados se eliminan cuando termina la ejecución. Un namespace que aportaste tú se deja intacto: su ciclo de vida es tuyo. Eventos: ai:agent:start, ai:agent:step (uno por paso emitido), ai:agent:done con la razón final, ai:agent:error.


En una arquitectura de microfrontends, conectar la app A con la app B es manual y crece n². El paradigma 4 sustituye ese cableado por un mapa de capacidades: cada app declara lo que sabe hacer, y el modelo resuelve los intents cruzando fronteras.

// En la micro-app de pedidos (React)
wu.ai.capability('orders', 'getRecent', {
description: 'Get the N most recent orders',
parameters: { limit: { type: 'number' } },
handler: async (p) => fetchOrders({ limit: p.limit || 10 }),
});
// En la micro-app del dashboard (Svelte)
wu.ai.capability('dashboard', 'updateKPIs', {
description: 'Refresh the KPI cards with the latest data',
handler: async () => { refreshKPIs(); return { updated: true }; },
});
// Desde cualquier sitio
const result = await wu.ai.intent('Show me the top customer by order count');
// → { content, tool_results, usage, resolved, appsInvolved }

Una capacidad es una acción registrada como app:name con la misma forma de configuración: misma API en sandbox, mismas reglas de permisos. Llama a wu.ai.removeApp('orders') en el unmount de la app para que no queden capacidades obsoletas en el mapa.

intent() acepta { plan, provider, temperature, maxTokens, signal, responseFormat }. plan es una pista — una lista ordenada como ['orders:getRecent', 'dashboard:updateKPIs'] — no un contrato.

Un workflow es una receta con nombre y parámetros. Hay dos modos, y el modo lo cambia todo en cuanto a coste y fiabilidad.

function workflow(name: string, config: {
description?: string;
steps: string[] | Array<{ action: string; [k: string]: any }>;
mode?: 'ai' | 'deterministic'; // se detecta automáticamente
parameters?: Record<string, { type: string; required?: boolean }>;
maxSteps?: number; // 15 por defecto (modo ai)
provider?: string;
temperature?: number; // 0.2 por defecto
}): Wu;
function runWorkflow(name: string, params?: object, options?: {
onStep?: Function;
shouldContinue?: Function;
signal?: AbortSignal;
}): AsyncGenerator<AgentStepResult>;

El modo se detecta automáticamente: si steps[0] es un objeto con una propiedad action, el workflow es deterministic; si no, es ai. Define mode de forma explícita si quieres asegurarte.

wu.ai.workflow('register-user', {
description: 'Register a new user in the system',
steps: [
'Navigate to the Customers section',
'Click the "Add Customer" button',
'Type "{{name}}" into the name field',
'Type "{{email}}" into the email field',
'Click Submit',
'Verify the success message appears',
],
parameters: { name: { type: 'string', required: true }, email: { type: 'string', required: true } },
});

{{param}} se sustituye en cada cadena de paso, los pasos se convierten en un objetivo y el bucle del agente lo ejecuta con las browser actions. Flexible, tolerante a cambios en la interfaz… y cuesta una ida y vuelta al LLM por paso.

wu.ai.workflow('register-user-fast', {
parameters: { name: { type: 'string', required: true }, email: { type: 'string', required: true } },
steps: [
{ action: 'navigate', section: 'customers' },
{ action: 'click', selector: '#add-customer' },
{ action: 'wait', selector: '#customer-form', timeout: 5000 },
{ action: 'type', selector: '#name', value: '{{name}}' },
{ action: 'type', selector: '#email', value: '{{email}}' },
{ action: 'click', text: 'Submit' },
{ action: 'wait', ms: 500 },
],
});
for await (const step of wu.ai.runWorkflow('register-user-fast', { name: 'Ada', email: 'ada@x.io' })) {
console.log(step.step, step.type, step.content);
}
action Campos Hace
click selector y/o text Hace clic por selector CSS o por texto visible
type selector, value, clear (por defecto true), submit (por defecto false) Escribe en un input, compatible con frameworks
navigate section o selector Emite nav:section, o hace clic en el selector
wait ms, o selector + timeout (por defecto 5000) Espera, o consulta hasta que aparezca un selector
emit event, data Emite en el bus de eventos como wu-ai
setState path, value Escribe en el store de wu

La interpolación {{param}} se aplica exactamente a estos campos: value, selector, text, section, event, path. Todo lo demás — data, ms, timeout — se usa literalmente.

Misma interfaz de generador que el agente, así que la misma UI renderiza ambos. Cero tokens, determinista, y se rompe en cuanto cambia un selector. Ese intercambio es justamente el punto: usa deterministic para los caminos que controlas y ai para los que no.

Si faltan parámetros obligatorios, lanza antes del primer paso. Ambos modos emiten ai:workflow:start (con mode) y ai:workflow:done.