---
title: "Crea tu propio agente de IA con Mastra (archivos, web, navegador)"
description: "Una guía paso a paso para construir un asistente de IA ligero con el framework Mastra: herramientas de archivos, shell, búsqueda web en vivo, automatización de navegador y memoria persistente."
date: 2026-06-22
categories: ["ai"]
tags: ["ai-tools","mastra","self-hosted"]
---

import Button from "@components/widgets/Button.astro";
import Notice from "@components/widgets/Notice.astro";
import ListCheck from "@components/widgets/ListCheck.astro";
import Accordion from "@components/widgets/Accordion.astro";
import Tabs from "@components/widgets/Tabs.astro";
import Tab from "@components/widgets/Tab.astro";

Llevo un año construyendo asistentes de IA de forma intermitente. Empecé con un bot de Discord en Agno (escribí sobre eso [aquí](/create-your-own-ai-agent/)), y funcionaba bien, pero seguía chocando con el mismo muro. Cada framework quería que pensara en sus términos. Define los agentes así. Conecta las herramientas de esta otra manera. La memoria va aquí, no allí. El boilerplate terminaba pesando más que la lógica real.

Así que cuando encontré [Mastra](https://mastra.ai/), sentí más curiosidad que convencimiento. Otro framework de agentes en TypeScript. Pero el planteamiento era diferente: un solo archivo para definir un agente, las herramientas son simplemente funciones con esquemas Zod, la memoria y el almacenamiento son intercambiables, y hay una UI de Studio integrada para chatear con tu agente e inspeccionar trazas. Sin YAML, sin editor de DAG, sin archivo de configuración de 200 líneas.

Migré mi asistente y lo liberé como open source. El resultado es un agente ligero y enfocado que puede buscar en la web, leer y escribir archivos, ejecutar comandos de shell, navegar páginas con mucho JavaScript y recordar lo que le dijiste entre sesiones. Sin bloat, sin circo de 15 agentes. Este artículo explica cómo lo construí, paso a paso, para que puedas crear el tuyo o simplemente clonar el mío y empezar a trastear.

<Button text="Ver el código en GitHub" link="https://github.com/bitdoze/mastra-assistant" variant="solid" color="purple" size="md" icon="github" />

## Qué hace realmente el asistente

<YouTubeEmbed
  url="https://www.youtube.com/embed/FdMQAyzsbbI"
  label="Construí un sistema de IA con 156M de tokens: así lo hice"
/>

Antes del cómo, aquí está el qué. El agente puede:

- Leer, escribir, editar y buscar archivos en un workspace local, y ejecutar comandos de shell (con aprobación)
- Buscar en la web en vivo y obtener contenido limpio de páginas a través de [TinyFish](https://go.bitdoze.com/tinyfish)
- Controlar un navegador Chromium real para navegar páginas renderizadas con JS, hacer clic, escribir y extraer datos
- Obtener metadatos y transcripciones de videos de YouTube para investigación
- Descubrir repositorios de GitHub en tendencia y obtener detalles del repo con el contenido del README
- Recordar cosas entre sesiones usando un embedder local (sin API key de embeddings)
- Ejecutarse según un horario (un workflow de digest diario de noticias que investiga IA, DevOps, self-hosting y Hacker News, y luego escribe un resumen en Markdown)
- Ser editado en tiempo de ejecución a través de la UI de Mastra Studio, sin necesidad de redesplegar

Sin bot de Discord, sin pipelines de video, sin publicaciones en redes sociales. Solo investigación y asistencia de programación. El repo en GitHub tiene el panorama completo.

## Requisitos previos

<ListCheck>
<ul>
<li><a href="https://nodejs.org/">Node.js 22+</a> instalado</li>
<li>Una <a href="https://openrouter.ai/keys">API key de OpenRouter</a> (cientos de modelos, hay niveles gratuitos)</li>
<li>Una API key de TinyFish (búsqueda y fetch web gratis, <a href="https://go.bitdoze.com/tinyfish">sin tarjeta de crédito</a>)</li>
<li>Chromium, solo si quieres automatización de navegador</li>
</ul>
</ListCheck>

Déjame explicar la capa del modelo, porque aquí tienes opciones reales.

El predeterminado es [OpenRouter](https://openrouter.ai). Es una puerta de enlace de modelos que te da acceso a más de 300 modelos detrás de una sola API key, incluyendo opciones gratuitas como Google Gemini 2.5 Flash, Meta Llama 3.3 70B y Qwen 3 Coder. Si quieres empezar sin gastar nada, funciona. Los modelos de pago cobran por token y son baratos. Explora la lista completa en [openrouter.ai/models](https://openrouter.ai/models).

Si prefieres una tarifa plana, [OpenCode Go](/es/opencode-go-plan/) es una alternativa. Por 10 $ al mes obtienes 16 modelos detrás de una sola API key (Grok 4.5, Kimi K3, DeepSeek V4 Pro, GLM-5.2, MiniMax M3 y otros). Lo cubrí en detalle en la [reseña de OpenCode Go](/es/opencode-go-plan/). Puedes cambiar entre proveedores modificando una variable de entorno.

<Button text="Explorar modelos de OpenRouter" link="https://openrouter.ai/models" variant="solid" color="green" size="md" icon="arrow-right" />
<Button text="Probar OpenCode Go (5 $ el primer mes)" link="https://go.bitdoze.com/opencode-go" variant="solid" color="purple" size="md" icon="arrow-right" />

La capa web es [TinyFish](/es/tinyfish-busqueda-web-agentes-ia/). La búsqueda y el fetch son gratuitos, y fetch renderiza las páginas en una instancia real de Chromium, así que los sitios de documentación con mucho JavaScript vuelven legibles. Escribí un artículo completo sobre por qué esto importa para los agentes de programación. Versión corta: la mayoría de la documentación moderna son SPAs, y un fetch de HTML crudo devuelve cascarones vacíos. TinyFish se encarga de eso por ti.

<Button text="Obtén una API key de TinyFish gratis" link="https://go.bitdoze.com/tinyfish" variant="solid" color="blue" size="md" icon="arrow-right" />

## Inicio rápido

Si quieres saltarte los pasos y trastear primero con una versión funcional, clona el repo y ejecuta la configuración.

```bash
git clone https://github.com/bitdoze/mastra-assistant.git
cd mastra-assistant
npm install
cp .env.example .env
# fill in your keys, then:
npm run dev
```

Abre `http://localhost:4111` y verás Mastra Studio. Haz clic en el agente `assistant` y empieza a chatear.

El resto de este artículo explica cómo está armado ese código, para que puedas construir el tuyo desde cero o modificar el mío.

## Paso 1: Crear el proyecto

Genera un nuevo proyecto Mastra:

```bash
npx create-mastra@latest my-agent
cd my-agent
npm install
```

Esto te da un esqueleto funcional con un agente del clima de ejemplo. Borra los archivos de ejemplo. Vamos a construir desde una base limpia.

## Paso 2: Instalar las dependencias

Esto es de lo que depende el proyecto. No necesitas todo esto el primer día, pero explicaré qué hace cada cosa a medida que agreguemos la función correspondiente.

```bash
npm install @mastra/core @mastra/memory @mastra/libsql @mastra/fastembed \
  @mastra/agent-browser @mastra/observability @mastra/duckdb \
  @mastra/editor @mastra/loggers @mastra/evals \
  @tiny-fish/sdk ws zod
```

Para la automatización del navegador, instala también Chromium:

```bash
npx playwright-core install chromium
```

## Paso 3: Definir el agente

Este es el núcleo de todo el proyecto. Un archivo, un agente. Aquí está la estructura que uso, adaptada de mi repo en `src/mastra/agents/assistant.ts`:

```typescript
import { Agent } from "@mastra/core/agent";
import { memory } from "../memory";
import { workspace } from "../workspaces";
import { browser } from "../browsers";
import { tinyfishSearch } from "../tools/tinyfish-search";
import { tinyfishFetch } from "../tools/tinyfish-fetch";
import { fetchYoutubeMetadata } from "../tools/youtube-metadata";
import { fetchYoutubeTranscript } from "../tools/youtube-transcript";
import { githubTrendingRepos } from "../tools/github-trending";
import { githubRepo } from "../tools/github-repo";

const AGENT_MODEL =
  process.env.AGENT_MODEL ?? "google/gemini-2.5-flash";

export const assistant = new Agent({
  id: "assistant",
  name: "Assistant",
  instructions: () => {
    const now = new Date();
    const iso = now.toISOString().split("T")[0];
    const year = String(now.getUTCFullYear());
    return `TODAY IS ${iso}. THE CURRENT YEAR IS ${year}. Use ${year} in all web searches.

You are a general-purpose coding and research assistant.

You can read, write, and edit files, run shell commands, search the web,
fetch YouTube transcripts, browse GitHub repos, and drive a browser.
Prefer doing real work with tools over guessing.
Cite URLs when answering from the web.`;
  },
  model: AGENT_MODEL,
  memory,
  workspace,
  browser,
  tools: {
    tinyfishSearch,
    tinyfishFetch,
    fetchYoutubeMetadata,
    fetchYoutubeTranscript,
    githubTrendingRepos,
    githubRepo,
  },
});
```

Algunas cosas que vale la pena señalar.

El campo `instructions` es una función, no un string. Se resuelve en cada llamada, así que la fecha actual inyectada en el system prompt siempre está actualizada. Lo aprendí por las malas después de que mi agente siguiera buscando cosas con la fecha del año pasado y obtuviera resultados obsoletos. Los modelos son malos sabiendo en qué año estamos. Díselo.

El campo `model` acepta un string en formato `provider/model`. `google/gemini-2.5-flash` se enruta a través de OpenRouter. Si usas OpenAI directamente, sería `openai/gpt-4o`. Mastra soporta docenas de proveedores de fábrica. También puedes usar [OpenCode Go](https://opencode.ai/go) configurando `AGENT_MODEL=opencode-go/glm-5.2` — consulta la [guía de OpenCode Go](/es/opencode-go-plan/) para la lista completa de modelos.

`memory`, `workspace` y `browser` son opcionales. Puedes empezar solo con `instructions`, `model` y `tools`, e ir agregando el resto sobre la marcha. Cubro cada uno más abajo.

## Paso 4: Conectar la instancia de Mastra

El agente no sirve de nada por sí solo. Necesitas registrarlo en una instancia de `Mastra`, que es lo que ejecuta el servidor, la UI de Studio, el almacenamiento y la autenticación. Crea `src/mastra/index.ts`:

```typescript
import { Mastra } from "@mastra/core/mastra";
import { PinoLogger } from "@mastra/loggers";
import { LibSQLStore } from "@mastra/libsql";
import { assistant } from "./agents/assistant";
import { auth } from "./auth";

export const mastra = new Mastra({
  agents: { assistant },
  storage: new LibSQLStore({
    url: process.env.DATABASE_URL ?? "file:./mastra.db",
  }),
  logger: new PinoLogger({ name: "Mastra", level: "info" }),
  server: {
    host: "0.0.0.0",
    port: 4111,
    auth,
  },
});
```

Ese es el mínimo. El almacenamiento usa LibSQL (SQLite), lo que significa que no hay ninguna base de datos externa que configurar. El archivo se crea automáticamente en la primera ejecución. Ejecuta `bun run dev` y tendrás un servidor de agente funcional con una UI de chat.

## Paso 5: Agregar memoria

De fábrica, el agente no tiene memoria entre conversaciones. Cada hilo empieza desde cero. Para un asistente real, eso no es suficiente. La clase `Memory` de Mastra maneja dos cosas: memoria de trabajo (un bloc persistente de datos del usuario) y recuerdo semántico (búsqueda vectorial sobre mensajes anteriores).

Este es mi `src/mastra/memory.ts`:

```typescript
import { Memory } from "@mastra/memory";
import { LibSQLVector } from "@mastra/libsql";
import { fastembed } from "@mastra/fastembed";

export const memory = new Memory({
  vector: new LibSQLVector({
    url: process.env.DATABASE_URL ?? "file:./mastra.db",
  }),
  embedder: fastembed,
  options: {
    semanticRecall: {
      topK: 3,
      messageRange: 2,
    },
    workingMemory: {
      enabled: true,
      scope: "resource",
      template: `# User Profile
## Identity
- Name:
- Timezone:
## Preferences
- Communication Style:
- Coding Conventions:
## Session State
- Active Task:
- Decisions Made:`,
    },
  },
});
```

La parte que quiero destacar es `embedder: fastembed`. Esto ejecuta el modelo de embeddings localmente a través de ONNX Runtime (bge-small-en-v1.5). Sin API key de embeddings de OpenAI, sin costo por token, nada sale de tu máquina. El modelo se descarga en el primer uso, unos 130 MB. Después de eso, el recuerdo semántico es gratis.

La memoria de trabajo tiene el ámbito `resource`, lo que significa que persiste en todos los hilos de un usuario determinado. El agente va rellenando esa plantilla con el tiempo: tu nombre, tu zona horaria, tus preferencias. La próxima vez que hables con él, incluso en un hilo nuevo, lo recuerda.

<Notice type="info" title="Si prefieres embeddings alojados">
Cambia `fastembed` por `new ModelRouterEmbeddingModel('openai/text-embedding-3-small')` y agrega una key de proveedor de embeddings. Lo local me funciona bien, pero si estás en un VPS pequeño sin margen de CPU, los embeddings alojados son más rápidos.
</Notice>

## Paso 6: Agregar herramientas de búsqueda web, YouTube y GitHub

Aquí es donde entra TinyFish. Las herramientas en Mastra son simplemente funciones con esquemas Zod de entrada y salida. Para saber cuándo mantener herramientas nativas frente a conectar servidores MCP (y el costo en RAM del MCP por stdio), consulta [herramientas de Mastra vs MCP](/es/herramientas-mastra-vs-mcp/). Aquí está la herramienta de búsqueda, de `src/mastra/tools/tinyfish-search.ts`:

```typescript
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import { getTinyFish } from "./tinyfish-client";

export const tinyfishSearch = createTool({
  id: "tinyfish_search",
  description:
    "Search the live web and return ranked results (title, snippet, url). Use for factual questions or finding pages to read.",
  inputSchema: z.object({
    query: z.string().describe("Search query. site: and -site: operators supported."),
    location: z.string().optional().describe("Country code for geo-targeting, e.g. US, GB."),
  }),
  outputSchema: z.object({
    results: z.array(
      z.object({
        title: z.string(),
        snippet: z.string(),
        url: z.string(),
        domain: z.string(),
      }),
    ),
  }),
  execute: async (input) => {
    const client = getTinyFish();
    const res = await client.search.query({ query: input.query });
    return {
      results: res.results.map((r) => ({
        title: r.title,
        snippet: r.snippet,
        url: r.url,
        domain: r.site_name,
      })),
    };
  },
});
```

La herramienta de fetch es similar pero llama a `client.fetch.getContents()` con hasta 10 URLs y devuelve Markdown limpio. El campo `description` importa más de lo que crees. Es lo que el modelo lee para decidir si usa la herramienta. Sé específico sobre cuándo recurrir a ella.

El cliente compartido vive en `tinyfish-client.ts` y solo envuelve el SDK:

```typescript
import { TinyFish } from "@tiny-fish/sdk";

let client: TinyFish | null = null;

export function getTinyFish(): TinyFish {
  if (!client) {
    client = new TinyFish(); // reads TINYFISH_API_KEY from env
  }
  return client;
}
```

Ambas herramientas son gratuitas. La búsqueda está limitada a 30 peticiones por minuto, el fetch a 150 URLs por minuto. Para un asistente personal es más que suficiente.

### Herramientas de YouTube

Dos herramientas de YouTube permiten al agente obtener metadatos y transcripciones de videos sin salir de la conversación. `fetch-youtube-metadata` obtiene título, canal, duración, número de vistas y miniatura a partir de una URL. `fetch-youtube-transcript` extrae la transcripción completa o los subtítulos. Útil para investigación: el agente puede ver el resumen de un video y citarlo en sus respuestas.

### Herramientas de GitHub

Dos herramientas de GitHub le dan al agente acceso al ecosistema open source. `github_trending_repos` descubre repositorios en tendencia de los últimos N días, filtrados por lenguaje si quieres. `github_repo` obtiene los detalles del repo y el contenido completo del README. El agente las usa para investigación: encontrar herramientas nuevas, ver qué es popular, leer documentación sin salir.

## Paso 7: Agregar un workspace para archivos y shell

El campo `workspace` le da al agente un sistema de archivos aislado y acceso al shell. Mastra lo maneja a través de la clase `Workspace`. Aquí hay una versión simplificada de mi `src/mastra/workspaces.ts`:

```typescript
import {
  Workspace,
  LocalFilesystem,
  LocalSandbox,
  WORKSPACE_TOOLS,
} from "@mastra/core/workspace";

export const workspace = new Workspace({
  id: "default",
  name: "Default Workspace",
  filesystem: new LocalFilesystem({
    basePath: "./workspace",
  }),
  sandbox: new LocalSandbox({ workingDirectory: "./workspace" }),
  bm25: true,
  tools: {
    enabled: true,
    [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: {
      requireApproval: true,
      requireReadBeforeWrite: true,
    },
    [WORKSPACE_TOOLS.FILESYSTEM.DELETE]: {
      enabled: false,
    },
    [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: {
      requireApproval: true,
      maxOutputTokens: 5000,
    },
  },
});
```

Esto agrega herramientas de archivos (leer, escribir, editar, grep, listar) y una herramienta de shell (ejecutar comandos). Los archivos escritos en el workspace son ejecutables de inmediato porque el filesystem y el sandbox apuntan al mismo directorio.

Fíjate en las medidas de seguridad. Las escrituras requieren aprobación y una verificación de lectura previa. La herramienta de borrado está completamente deshabilitada. Los comandos de shell requieren aprobación por defecto. Puedes desactivar las aprobaciones con `REQUIRE_COMMAND_APPROVAL=false` en `.env` para configuraciones locales de confianza, pero yo no lo haría en un servidor compartido.

También puedes darle al agente acceso a otros directorios de tu máquina a través de `ALLOWED_DIRECTORIES`. Yo lo uso para que el agente trabaje en varios proyectos. El confinamiento sigue activo; solo tiene un patio más grande.

## Paso 8: Agregar automatización de navegador

El campo `browser` habilita una instancia local de Playwright que el agente puede controlar. Cada hilo de conversación obtiene su propio navegador aislado. Dieciséis herramientas: navegar, snapshot, clic, escribir, scroll, captura de pantalla, evaluar JavaScript y más.

```typescript
import { AgentBrowser } from "@mastra/agent-browser";

const headless = process.env.BROWSER_HEADLESS !== "false";
const cdpUrl = process.env.BROWSER_CDP_URL;

export const browser = new AgentBrowser(
  cdpUrl
    ? { headless, cdpUrl, scope: "shared" }
    : { headless, scope: "thread" },
);
```

El navegador es opcional. Si no se lo pasas al agente, las herramientas de navegador simplemente no existen. Yo lo mantengo activado porque a veces el agente necesita navegar un sitio de documentación que requiere JavaScript, o pasar por un flujo de login para llegar a contenido detrás de autenticación.

Hay una transmisión en vivo que llega a Studio por WebSocket, así que puedes ver al agente haciendo clic en tiempo real. Es curiosamente divertido de ver.

## Paso 9: Agregar una skill de investigación

Las skills son workflows reutilizables que el agente adopta automáticamente. Crea una carpeta en `workspace/skills/research/` con un `SKILL.md`:

```markdown
---
name: research
version: 1.0.0
---

# Research workflow
1. Search the web with `tinyfish_search` for the topic.
2. Pick the top 3-5 results.
3. Fetch each with `tinyfish_fetch` to get clean content.
4. Synthesize a summary with sources cited.
```

El agente lee los archivos de skills en la siguiente petición y los aplica cuando son relevantes. Puedes agregar más skills para tareas recurrentes: checklists de revisión de código, patrones de investigación de contenido, lo que sea que te encuentres repitiendo.

## Paso 10: Agregar un workflow programado

Un workflow de digest diario de noticias se ejecuta según un horario, investiga en la web y escribe un resumen en Markdown en el workspace. Mastra lo registra automáticamente al arrancar.

```typescript
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";

const digestStep = createStep({
  id: "generate-digest",
  inputSchema: z.object({ topic: z.string().optional() }),
  outputSchema: z.object({ ok: z.boolean(), path: z.string().optional() }),
  execute: async ({ inputData, mastra }) => {
    const today = new Date().toISOString().split("T")[0];
    const agent = mastra.getAgent("assistant");

    const result = await agent.generate(
      `Research today's top stories (${today}) on ${
        inputData.topic ??
        "AI news, DevOps updates, self-hosting, trending GitHub repos, and Hacker News"
      }. Summarize the top stories as markdown with sources.`,
      { memory: { thread: `digest-${today}`, resource: "workflow" } },
    );

    return { ok: true, path: `workspace/digests/news-${today}.md` };
  },
});

export const newsDigest = createWorkflow({
  id: "news-digest",
  inputSchema: z.object({ topic: z.string().optional() }),
  outputSchema: z.object({ ok: z.boolean(), path: z.string().optional() }),
  schedule: {
    cron: "30 7 * * *",
    timezone: process.env.AGENT_TIMEZONE ?? "Europe/Bucharest",
    inputData: {},
  },
})
  .then(digestStep)
  .commit();
```

Puedes pausar y reanudar las programaciones desde Studio o la API. El workflow reutiliza el mismo agente, así que tiene acceso a todas las mismas herramientas. Los digests quedan en `workspace/digests/news-YYYY-MM-DD.md`.

## Paso 11: Configurar las variables de entorno

Aquí está el archivo `.env` con las claves que necesitas:

```env
# Model provider (OpenRouter by default)
OPENROUTER_API_KEY=your-openrouter-key
AGENT_MODEL=google/gemini-2.5-flash

# Web search and fetch (free)
TINYFISH_API_KEY=sk-tinyfish-your-key

# GitHub API (recommended, raises rate limits)
GITHUB_TOKEN=your-github-token

# Storage
TURSO_DATABASE_URL=file:./mastra.db

# Auth tokens for Studio login
ADMIN_API_KEY=your-admin-token

# Optional
# AGENT_TIMEZONE=Europe/Bucharest
# ALLOWED_DIRECTORIES=/home/me/projects/other-app
# REQUIRE_COMMAND_APPROVAL=false
```

Para usar OpenCode Go en su lugar, cambia las variables del proveedor:

```env
OPENCODE_API_KEY=your-opencode-go-key
AGENT_MODEL=opencode-go/glm-5.2
```

Mastra detecta el proveedor por el prefijo de la key. Puedes mezclar y combinar: usa OpenRouter para el agente principal y OpenCode Go para un modelo juez, o la combinación que tenga sentido para tu presupuesto. La [guía de OpenCode Go](/es/opencode-go-plan/) tiene la lista completa de modelos y el desglose de precios.

## Paso 12: Ejecutarlo

```bash
npm run dev
```

Abre `http://localhost:4111` para acceder a Mastra Studio.

Studio te da:

- Una interfaz de chat para hablar con el agente
- Una vista de trazas para inspeccionar cada llamada de herramienta, conteo de tokens y latencia
- Un explorador del workspace para ver los archivos a los que el agente tiene acceso
- Una vista de programaciones para gestionar workflows
- Una pestaña de editor para modificar las instrucciones y herramientas del agente en tiempo de ejecución

La pestaña Editor merece una pausa. Te permite cambiar el system prompt del agente y las definiciones de herramientas a través de la UI, con versionado de borrador, publicado y archivado guardado en la base de datos. Sin cambios de código, sin redespliegue. Yo lo uso para ajustar el comportamiento del agente durante el día sin reiniciar el servidor.

<Accordion label="¿Qué me permite cambiar realmente el Editor?" group="faq">
Las instrucciones del agente (el system prompt) y su lista de herramientas. Los guardados crean borradores versionados en la base de datos. Puedes publicar un borrador para activarlo, o volver a una versión archivada. Si quieres bloquear el agente, pon `editor: false` en el constructor del agente. Si quieres permitir solo ediciones del prompt, usa `editor: { instructions: true }`.
</Accordion>

## Despliegue en un VPS

Ejecutarlo localmente está bien para pruebas, pero un asistente real debería estar disponible 24/7. Yo despliego el mío en un VPS de Hetzner detrás de Caddy como proxy inverso.

<Button text="Consigue 20 € de crédito en Hetzner" link="https://go.bitdoze.com/hetzner" variant="solid" color="green" size="md" icon="rocket-launch" />
<Button text="Probar VPS de Hostinger" link="https://go.bitdoze.com/hostinger-vps" variant="outline" color="blue" size="md" icon="rocket-launch" />

_Los precios de los VPS subieron en todos los proveedores en 2026 — si estás reconsiderando un servidor alquilado, mira [qué cambió y cuándo gana un mini PC](/es/subidas-precios-vps/)._

Un CX22 (2 vCPU, 4 GB de RAM) lo maneja sin problema. El agente en sí no consume muchos recursos. El navegador es lo único que come memoria, y solo cuando está ejecutándose activamente.

### Compilar y ejecutar con pm2

Compila el bundle de producción:

```bash
npm run build
```

Esto produce un servidor autocontenido en `.mastra/output/`. Luego usa pm2 para mantenerlo corriendo:

```bash
npm install -g pm2
pm2 start .mastra/output/index.mjs --name mastra-assistant
pm2 save
pm2 startup
```

Ese último comando genera el script de inicio para que pm2 arranque con el sistema. Revisa los logs con `pm2 logs mastra-assistant`.

### Proxy inverso con Caddy

Caddy maneja TLS automáticamente. Agrega esto a tu Caddyfile:

```caddy
your-domain.com {
    reverse_proxy localhost:4111
}
```

Una cosa que me dio problemas: detrás de un proxy inverso, pon `MASTRA_AUTO_DETECT_URL=true` en tu `.env`. Sin eso, Studio intenta llamar a `0.0.0.0:4111` desde el navegador, lo cual no funciona. Ese flag hace que Studio use el origen del navegador en su lugar.

### Ejecutar en macOS

Los mismos pasos de compilación y pm2 funcionan en un Mac Mini o MacBook. Si quieres acceder a Studio desde otros dispositivos de tu red sin reenvío de puertos, instala [Tailscale](https://tailscale.com):

```bash
brew install tailscale
sudo tailscale up
```

Studio queda entonces disponible en `http://your-mac:4111` desde cualquier dispositivo de tu tailnet.

## Qué aprendí

Algunas observaciones de haberlo usado durante un tiempo.

El enfoque de un solo agente funciona. El modelo elige la herramienta correcta por sí solo la mayoría de las veces. No necesitas cinco agentes especializados para hacer trabajo útil. Empieza con un agente y un conjunto enfocado de herramientas. Agrega complejidad solo cuando realmente la necesites.

Los embeddings locales son más lentos que los alojados en la primera ejecución (el modelo tiene que descargarse, unos 130 MB), pero después funcionan bien para un asistente personal. Gratis, nada sale de tu máquina. Si estás construyendo algo con muchos usuarios concurrentes, cambia a embeddings alojados para mantener baja la latencia.

La automatización del navegador es potente pero cara en memoria y tiempo. La mantengo activada, pero el agente rara vez recurre a ella. La mayoría de las tareas web las resuelven la búsqueda y el fetch. El navegador es para cuando un sitio necesita JavaScript o interacción para renderizarse.

## Cómo TinyFish impulsa todo el pipeline

Quiero dedicar más tiempo a la capa web porque es la pieza que marcó la mayor diferencia. Antes de TinyFish, había escrito y descartado tres configuraciones de scraping distintas. Wrappers de BeautifulSoup, scripts de Playwright con selectores personalizados, incluso un parser de DOM a medio terminar que intentaba extraer texto de artículos del HTML crudo. Cada uno funcionó unas dos semanas antes de que un sitio cambiara su diseño o añadiera un muro antibots y todo se rompiera.

El verdadero desbloqueo fue darme cuenta de que mis agentes no necesitaban HTML crudo en absoluto. Necesitaban texto limpio y estructurado sobre el que razonar. La búsqueda de TinyFish devuelve resultados clasificados con títulos, snippets y URLs. El fetch de TinyFish toma esas URLs, hasta 10 a la vez, renderiza las páginas en una instancia real de Chromium (así que los sitios de documentación con mucho JavaScript funcionan de verdad), elimina la navegación, los anuncios, los scripts y el desorden, y devuelve Markdown limpio. Eso es todo. Esa es toda la interfaz.

![Diagrama del pipeline del agente con Mastra y TinyFish](@assets/images/26/06/mastra-tinyfish-diagram.webp)

### Cómo es realmente el pipeline diario

Cada mañana a las 7:30, se dispara un cron programado. Activa agentes de investigación que buscan noticias sobre IA, DevOps, self-hosting, repositorios de GitHub en tendencia y Hacker News. Cada agente llama a `tinyfish_search` con consultas dirigidas, elige los mejores resultados y pasa esas URLs a `tinyfish_fetch` para una recuperación en paralelo. En segundos, el agente tiene el texto completo de hasta 10 páginas web en vivo en su ventana de contexto.

A partir de ahí, los agentes hacen trabajo real con ese contenido. Verifican afirmaciones técnicas contra la documentación en vivo antes de redactar artículos. Extraen notas de versión y changelogs para comprobar si la lista de funciones de una herramienta está actualizada. Cruzan múltiples fuentes para detectar imprecisiones. El digest diario llega al workspace como un archivo Markdown con las fuentes citadas, y nadie tocó un teclado.

El pipeline de video funciona igual. Un trabajo programado obtiene documentación en vivo y notas de versión vía TinyFish, verifica la precisión técnica y genera un guion de video completamente animado que se renderiza a través de HyperFrames. El agente pasa de una consulta de búsqueda cruda a un artículo publicado y un video renderizado sin intervención manual.

### Por qué esto importa específicamente para los agentes

La mayoría de las APIs de búsqueda web devuelven snippets, 160 caracteres de contexto por resultado. Es suficiente para decidir si un enlace es relevante, pero no para razonar sobre él. Acabas en un bucle: buscar, hacer clic, leer, volver, buscar de nuevo. Para un humano eso es normal. Para un agente, desperdicia tokens y tiempo en navegación en lugar de pensar.

El fetch de TinyFish resuelve esto devolviendo el Markdown de la página completa en una sola llamada. El agente obtiene el contenido real, no un adelanto. Puede responder preguntas, resumir, verificar hechos y sintetizar a través de múltiples fuentes sin saltar entre páginas. Y como la salida es Markdown, no HTML, no estás pagando por barras de navegación, pies de página y banners de cookies en tu presupuesto de tokens.

La velocidad también importa. Obtener 10 páginas en paralelo mantiene baja la latencia del bucle del agente. Una tarea de investigación que a mí me llevaría 30 minutos saltando entre pestañas le toma al agente unos 15 segundos. Lo medí. El cuello de botella es la inferencia del LLM, no la recuperación web.

### Qué le diría a alguien que construye esto

No escribas scrapers personalizados para agentes de IA. El scraping y la extracción web son problemas resueltos. Dale a tus agentes primitivas de alto nivel: una búsqueda que devuelve resultados clasificados y un fetch que devuelve texto limpio. Mantén las definiciones de herramientas simples. Reduce los tokens del prompt, evita que los agentes se pierdan en HTML crudo, y significa que nunca tendrás que depurar un selector CSS roto a las 2 de la mañana.

Escribí un artículo completo sobre [por qué TinyFish importa para los agentes de programación](/es/tinyfish-busqueda-web-agentes-ia/) si quieres la inmersión técnica en la API, los proyectos del cookbook y cómo conectarlo a otros frameworks.

## De aquí en adelante

- [Clona el proyecto completo](https://github.com/bitdoze/mastra-assistant) y adáptalo. El README cubre todas las herramientas, workflows, skills y el despliegue.
- Lee la [documentación de Mastra](https://mastra.ai/docs/) para la API completa. Cubrí las piezas que uso, pero hay más (evals, workflows multiagente, pipelines RAG).
- Agrega herramientas de medios después: [agente de imágenes con Kie.ai](/es/agente-imagen-mastra-kie-ai/) y [TTS con clonación de voz con Fish Audio](/es/mastra-fish-audio-tts/).
- Elegir entre `createTool` nativo y servidores MCP: [herramientas de Mastra vs MCP](/es/herramientas-mastra-vs-mcp/).
- Las skills también funcionan en agentes de programación: las [mejores skills de IA para diseño web](/es/mejores-skills-ia-diseno-web/) compara 11 packs de `SKILL.md` que arreglan la UI genérica generada por IA.
- Si quieres un enfoque diferente para construir un bot de IA en Discord, mi [guía de Agno](/create-your-own-ai-agent/) cubre un enfoque basado en Python con orquestación de equipos y un sistema de memoria distinto.
- Para la capa de búsqueda web, la [guía de TinyFish](/es/tinyfish-busqueda-web-agentes-ia/) profundiza en la API, los proyectos del cookbook y cómo conectarlo a otros agentes como Hermes y Pi.
- Para la capa del modelo, [OpenRouter](https://openrouter.ai/models) tiene más de 300 modelos incluyendo niveles gratuitos. La [reseña de OpenCode Go](/es/opencode-go-plan/) cubre la alternativa de tarifa plana con 16 modelos por 10 $/mes.
- Si quieres comparar este enfoque con otros asistentes siempre activos, mira la [guía de configuración de OpenClaw](/es/guia-configuracion-openclaw/) y la [guía de configuración de OpenCode](/es/guia-configuracion-opencode/).
- Para el mapa más amplio de la IA open source (frameworks, memoria, gateways, asistentes), mira los [mejores repos de IA en GitHub](/es/mejores-repos-github-ia/).

<Button text="Explorar modelos de OpenRouter" link="https://openrouter.ai/models" variant="solid" color="green" size="md" icon="arrow-right" />
<Button text="Probar OpenCode Go (5 $ el primer mes)" link="https://go.bitdoze.com/opencode-go" variant="solid" color="purple" size="md" icon="arrow-right" />
<Button text="Obtén una API key de TinyFish gratis" link="https://go.bitdoze.com/tinyfish" variant="solid" color="blue" size="md" icon="arrow-right" />

<Notice type="success" title="Ya tienes un agente">
Un archivo para el agente, un puñado de funciones de herramientas y una instancia de Mastra para unirlo todo. Clona el repo, rellena dos API keys y estarás chateando con un asistente que puede leer tus archivos, buscar en la web, navegar GitHub y recordar lo que le dijiste.
</Notice>