Agentes Autónomos con Claude
Arquitectura completa · 7 agentes · cron jobs · el sistema que trabaja mientras dormís
Mapa rápido · salta al paso que te interese
Arquitectura de Agentes: cómo estructurar el sistema
Un sistema de agentes tiene 3 capas: el scheduler que dispara, el orchestrator que coordina, y los agentes que ejecutan. Cada agente es independiente y puede fallar sin tumbar el sistema.
| Capa | Responsabilidad | Tech Stack |
|---|---|---|
| Scheduler | Dispara agentes en horarios | Vercel Cron / GitHub Actions |
| Orchestrator | Coordina y maneja errores | Next.js API Routes |
| Agents | Ejecutan tareas específicas | Claude API + Tools |
| Storage | Persiste estado y outputs | Supabase |
| Notifications | Alerta cuando algo falla | Resend / Slack |
/app/api/cron/
├── oracle/route.ts # Dispara ORACLE diario
├── vera/route.ts # Dispara VERA semanal
├── repo/route.ts # Dispara REPO diario
└── signal/route.ts # Dispara SIGNAL lunes
/lib/agents/
├── base.ts # Clase base AgentRunner
├── oracle.ts # Lógica de ORACLE
├── vera.ts # Lógica de VERA
├── repo.ts # Lógica de REPO
└── types.ts # Tipos compartidos
/lib/tools/
├── web-scraper.ts # Tool de scraping
├── email-sender.ts # Tool de emails
├── db-writer.ts # Tool de DB
└── index.ts # Registry de tools// lib/agents/base.ts
import Anthropic from '@anthropic-ai/sdk'
import { createClient } from '@supabase/supabase-js'
export interface AgentConfig {
name: string
systemPrompt: string
tools: Anthropic.Tool[]
maxTokens?: number
model?: string
}
export interface AgentResult {
success: boolean
output: string
tokensUsed: number
duration: number
error?: string
}
export class AgentRunner {
private client: Anthropic
private supabase: ReturnType<typeof createClient>
private config: AgentConfig
constructor(config: AgentConfig) {
this.config = config
this.client = new Anthropic()
this.supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
)
}
async run(userMessage: string): Promise<AgentResult> {
const startTime = Date.now()
let tokensUsed = 0
try {
// Loop de agente: sigue hasta que no haya más tool calls
let messages: Anthropic.MessageParam[] = [
{ role: 'user', content: userMessage }
]
while (true) {
const response = await this.client.messages.create({
model: this.config.model || 'claude-sonnet-4-20250514',
max_tokens: this.config.maxTokens || 4096,
system: this.config.systemPrompt,
tools: this.config.tools,
messages,
})
tokensUsed += response.usage.input_tokens + response.usage.output_tokens
// Si no hay tool calls, terminamos
if (response.stop_reason === 'end_turn') {
const textContent = response.content.find(c => c.type === 'text')
return {
success: true,
output: textContent?.text || '',
tokensUsed,
duration: Date.now() - startTime,
}
}
// Procesar tool calls
const toolCalls = response.content.filter(c => c.type === 'tool_use')
const toolResults: Anthropic.MessageParam = {
role: 'user',
content: await Promise.all(
toolCalls.map(async (tc) => ({
type: 'tool_result' as const,
tool_use_id: tc.id,
content: await this.executeTool(tc.name, tc.input),
}))
),
}
messages.push({ role: 'assistant', content: response.content })
messages.push(toolResults)
}
} catch (error) {
return {
success: false,
output: '',
tokensUsed,
duration: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
private async executeTool(name: string, input: unknown): Promise<string> {
// Implementar cada tool aquí
const tools: Record<string, (input: unknown) => Promise<string>> = {
web_search: async (i) => this.webSearch(i as { query: string }),
save_to_db: async (i) => this.saveToDb(i as { table: string; data: unknown }),
send_email: async (i) => this.sendEmail(i as { to: string; subject: string; body: string }),
}
const tool = tools[name]
if (!tool) throw new Error(`Tool not found: ${name}`)
return tool(input)
}
private async webSearch(input: { query: string }): Promise<string> {
// Implementar búsqueda web
return JSON.stringify({ results: [] })
}
private async saveToDb(input: { table: string; data: unknown }): Promise<string> {
const { error } = await this.supabase.from(input.table).insert(input.data)
if (error) throw error
return 'Saved successfully'
}
private async sendEmail(input: { to: string; subject: string; body: string }): Promise<string> {
// Implementar envío de email
return 'Email sent'
}
}Los 7 Agentes: código completo de cada uno
Cada agente tiene un rol específico. Ninguno se superpone con otro. Juntos cubren las operaciones de un negocio digital.
Curador de noticias de IA. Busca, filtra, reescribe, y publica artículos diarios.
| Campo | Valor |
|---|---|
| Schedule | 08:00 UTC diario |
| Duración | 2-5 minutos |
| Output | 3 artículos en news_articles |
| Tokens/run | ~15,000 |
import { AgentRunner } from './base'
const ORACLE_SYSTEM = `Sos ORACLE, el editor de inteligencia de Renderz Studio.
Tu trabajo:
1. Buscar las 10 noticias más relevantes de IA de las últimas 24h
2. Filtrar las 3 más importantes para nuestra audiencia
3. Reescribir cada una en español, con tu análisis
4. Guardarlas en la base de datos
Audiencia: developers y emprendedores hispanohablantes que quieren usar IA en sus negocios.
<rules>
- NO cubrir gossip o drama de la industria
- SÍ cubrir: nuevos modelos, herramientas, técnicas, casos de uso
- Cada artículo: 200-300 palabras
- Tono: directo, técnico pero accesible
- Incluir un "Por qué importa" en cada artículo
</rules>`
const ORACLE_TOOLS = [
{
name: 'web_search',
description: 'Busca noticias en la web',
input_schema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Query de búsqueda' },
},
required: ['query'],
},
},
{
name: 'save_article',
description: 'Guarda un artículo en la DB',
input_schema: {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string' },
source_url: { type: 'string' },
category: { type: 'string', enum: ['modelos', 'herramientas', 'tecnicas', 'negocio'] },
},
required: ['title', 'content', 'category'],
},
},
]
export async function runOracle() {
const agent = new AgentRunner({
name: 'ORACLE',
systemPrompt: ORACLE_SYSTEM,
tools: ORACLE_TOOLS,
maxTokens: 8192,
})
return agent.run('Ejecuta tu rutina diaria: busca, filtra, y publica las noticias de hoy.')
}Cron Jobs en Vercel
Vercel Cron Jobs disparan tus agentes en horarios. Free tier: 2 jobs. Pro: ilimitados.
{
"crons": [
{
"path": "/api/cron/oracle",
"schedule": "0 8 * * *"
},
{
"path": "/api/cron/vera",
"schedule": "0 9 * * 5"
},
{
"path": "/api/cron/repo",
"schedule": "0 10 * * *"
},
{
"path": "/api/cron/signal",
"schedule": "0 10 * * 1"
}
]
}import { NextRequest } from 'next/server'
import { runOracle } from '@/lib/agents/oracle'
export async function GET(req: NextRequest) {
// Verificar auth
const authHeader = req.headers.get('authorization')
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const result = await runOracle()
// Log para debugging
console.log(`ORACLE completed: ${result.success ? 'SUCCESS' : 'FAILED'}`)
console.log(`Tokens: ${result.tokensUsed}, Duration: ${result.duration}ms`)
if (!result.success) {
// Notificar error
await fetch(process.env.SLACK_WEBHOOK!, {
method: 'POST',
body: JSON.stringify({
text: `🔴 ORACLE failed: ${result.error}`,
}),
})
}
return Response.json(result)
} catch (error) {
return Response.json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}, { status: 500 })
}
}⚠ Advertencia
Authorization: Bearer CRON_SECRET. Sin validarlo, cualquiera puede disparar tus agentes.Tip Pro
agent_schedules en Supabase. Así puedes cambiar schedules sin redeploy."Un agente bien configurado genera más valor dormido que vos despierto. El primer agente que automatices te va a parecer magia. El séptimo va a ser tu nuevo normal."
— El Arsenal, Renderz Studio
¿Quieres el sistema completo?
500+ páginas, 25 capítulos, código de producción, acceso a todas las guías del Arsenal.
Curso Completo · €29PayPal · Acceso inmediato