All skills
jwynia avatar

/mastra-hono

@99a8797
by J Wyniajwynia/agent-skills160 stars
20

Develop AI agents, tools, and workflows with Mastra v1 Beta and Hono servers. This skill should be used when creating Mastra agents, defining tools with Zod schemas, building workflows with step data flow, setting up Hono API servers with Mastra adapters, or implementing agent networks. Keywords: mastra, hono, agent, tool, workflow, AI, LLM, typescript, API, MCP.

Use this Skill: https://skilld.dev/gh/jwynia/agent-skills/mastra-hono

This session only. Nothing lands on disk.

referencesrag-memory-patterns.md

≈3.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

RAG and Memory Patterns

Guide to implementing Retrieval-Augmented Generation (RAG) and conversation memory in Mastra.

Overview

Mastra supports multiple memory and retrieval patterns:

  • Conversation Memory - Thread-based message history
  • Vector Storage - Semantic search over documents
  • RAG Integration - Combining retrieval with generation
  • Hybrid Search - Vector + keyword search

Conversation Memory

Basic Memory Configuration

import { Mastra } from "@mastra/core/mastra";
import { LibSQLStore } from "@mastra/libsql";

const mastra = new Mastra({
  agents: { myAgent },
  storage: new LibSQLStore({
    url: "file:./mastra.db",
  }),
});

Using Memory in Agent Calls

// First interaction
const response1 = await agent.generate("My name is Alex and I work at TechCorp", {
  memory: {
    thread: "conversation-123",  // Unique thread ID
    resource: "user-456",        // User identifier
  },
});

// Later in same thread - agent remembers context
const response2 = await agent.generate("What company do I work for?", {
  memory: {
    thread: "conversation-123",
    resource: "user-456",
  },
});
// Agent responds: "You work at TechCorp"

Thread Management

// Create new thread
const thread = await mastra.storage?.createThread({
  resourceId: "user-456",
  metadata: {
    topic: "customer-support",
    createdAt: new Date().toISOString(),
  },
});

// List threads for a user
const threads = await mastra.storage?.listThreads({
  resourceId: "user-456",
  page: 1,
  perPage: 10,
});

// Get thread messages
const messages = await mastra.storage?.getMessages({
  threadId: "thread-123",
  page: 1,
  perPage: 50,
});

// Delete thread
await mastra.storage?.deleteThread("thread-123");

Message History Window

// Limit context window for cost/performance
const response = await agent.generate("Continue our conversation", {
  memory: {
    thread: "conversation-123",
    resource: "user-456",
    options: {
      maxMessages: 10,     // Last 10 messages
      maxTokens: 4000,     // Or token limit
    },
  },
});

Vector Storage

Setting Up Vector Store

import { Mastra } from "@mastra/core/mastra";
import { PgVector } from "@mastra/pg-vector";

const mastra = new Mastra({
  agents: { myAgent },
  vectors: {
    default: new PgVector({
      connectionString: process.env.DATABASE_URL,
      tableName: "embeddings",
    }),
  },
});

Indexing Documents

import { embedMany } from "@ai-sdk/openai";

// Chunk and embed documents
const documents = [
  { id: "doc-1", content: "Mastra is a TypeScript AI framework...", metadata: { source: "docs" } },
  { id: "doc-2", content: "Agents in Mastra can use tools...", metadata: { source: "docs" } },
];

// Generate embeddings
const { embeddings } = await embedMany({
  model: openai.embedding("text-embedding-3-small"),
  values: documents.map(d => d.content),
});

// Store in vector database
await mastra.vectors?.default.upsert({
  indexName: "knowledge-base",
  vectors: documents.map((doc, i) => ({
    id: doc.id,
    vector: embeddings[i],
    metadata: {
      content: doc.content,
      ...doc.metadata,
    },
  })),
});

Querying Vectors

import { embed } from "@ai-sdk/openai";

// Embed query
const { embedding } = await embed({
  model: openai.embedding("text-embedding-3-small"),
  value: "How do Mastra agents work?",
});

// Search
const results = await mastra.vectors?.default.query({
  indexName: "knowledge-base",
  queryVector: embedding,
  topK: 5,
  filter: { source: "docs" },
});

// results: [{ id, score, metadata: { content, source } }, ...]

RAG Implementation

Basic RAG Tool

export const ragTool = createTool({
  id: "knowledge-search",
  description: "Search the knowledge base for relevant information",
  inputSchema: z.object({
    query: z.string().describe("Search query"),
    limit: z.number().optional().default(5),
  }),
  outputSchema: z.object({
    results: z.array(z.object({
      content: z.string(),
      score: z.number(),
      source: z.string(),
    })),
  }),
  execute: async (input, context) => {
    const { query, limit } = input;
    const { mastra } = context;

    // Embed query
    const { embedding } = await embed({
      model: openai.embedding("text-embedding-3-small"),
      value: query,
    });

    // Search
    const results = await mastra?.vectors?.default.query({
      indexName: "knowledge-base",
      queryVector: embedding,
      topK: limit,
    });

    return {
      results: results?.map(r => ({
        content: r.metadata.content,
        score: r.score,
        source: r.metadata.source,
      })) || [],
    };
  },
});

RAG-Enabled Agent

const ragAgent = new Agent({
  name: "rag-agent",
  instructions: `You are a helpful assistant with access to a knowledge base.

When answering questions:
1. First search the knowledge base for relevant information
2. Use the retrieved information to inform your response
3. Cite sources when possible
4. If the knowledge base doesn't have relevant info, say so`,
  model: openai("gpt-4o-mini"),
  tools: { ragTool },
});

Advanced RAG with Reranking

export const advancedRagTool = createTool({
  id: "advanced-search",
  description: "Search with semantic reranking",
  inputSchema: z.object({
    query: z.string(),
    limit: z.number().optional().default(10),
  }),
  execute: async (input, context) => {
    const { query, limit } = input;
    const { mastra } = context;

    // Initial retrieval (over-fetch)
    const { embedding } = await embed({
      model: openai.embedding("text-embedding-3-small"),
      value: query,
    });

    const candidates = await mastra?.vectors?.default.query({
      indexName: "knowledge-base",
      queryVector: embedding,
      topK: limit * 3, // Fetch 3x for reranking
    });

    // Rerank with LLM
    const reranker = new Agent({
      name: "reranker",
      model: openai("gpt-4o-mini"),
      instructions: "Score relevance 0-10 for each document to the query.",
    });

    const reranked = await Promise.all(
      candidates?.map(async (c) => {
        const response = await reranker.generate(
          `Query: ${query}\nDocument: ${c.metadata.content}\nScore (0-10):`,
          { output: z.object({ score: z.number() }) }
        );
        return { ...c, rerankedScore: response.object.score };
      }) || []
    );

    // Sort by reranked score and take top results
    return {
      results: reranked
        .sort((a, b) => b.rerankedScore - a.rerankedScore)
        .slice(0, limit)
        .map(r => ({
          content: r.metadata.content,
          score: r.rerankedScore,
        })),
    };
  },
});

Hybrid Search

Combining Vector and Keyword Search

export const hybridSearchTool = createTool({
  id: "hybrid-search",
  description: "Search using both semantic and keyword matching",
  inputSchema: z.object({
    query: z.string(),
    keywords: z.array(z.string()).optional(),
  }),
  execute: async (input, context) => {
    const { query, keywords } = input;
    const { mastra } = context;

    // Semantic search
    const { embedding } = await embed({
      model: openai.embedding("text-embedding-3-small"),
      value: query,
    });

    const semanticResults = await mastra?.vectors?.default.query({
      indexName: "knowledge-base",
      queryVector: embedding,
      topK: 10,
    });

    // Keyword search (if storage supports full-text search)
    const keywordResults = keywords?.length
      ? await mastra?.storage?.search({
          query: keywords.join(" "),
          limit: 10,
        })
      : [];

    // Merge and deduplicate
    const merged = new Map();
    semanticResults?.forEach(r => {
      merged.set(r.id, { ...r, semanticScore: r.score, keywordScore: 0 });
    });
    keywordResults?.forEach((r: any) => {
      if (merged.has(r.id)) {
        merged.get(r.id).keywordScore = r.score;
      } else {
        merged.set(r.id, { ...r, semanticScore: 0, keywordScore: r.score });
      }
    });

    // Combine scores (weighted average)
    const results = Array.from(merged.values())
      .map(r => ({
        ...r,
        combinedScore: r.semanticScore * 0.7 + r.keywordScore * 0.3,
      }))
      .sort((a, b) => b.combinedScore - a.combinedScore);

    return { results: results.slice(0, 10) };
  },
});

Memory Consolidation

Summarizing Long Conversations

const summarizeThread = async (threadId: string) => {
  const messages = await mastra.storage?.getMessages({
    threadId,
    page: 1,
    perPage: 100,
  });

  const summarizer = new Agent({
    name: "summarizer",
    model: openai("gpt-4o-mini"),
    instructions: "Create a concise summary of the conversation.",
  });

  const conversation = messages
    ?.map(m => `${m.role}: ${m.content}`)
    .join("\n");

  const summary = await summarizer.generate(
    `Summarize this conversation:\n\n${conversation}`
  );

  // Store summary as new message type
  await mastra.storage?.addMessage({
    threadId,
    role: "system",
    content: `[SUMMARY] ${summary.text}`,
    metadata: { type: "summary", originalMessageCount: messages?.length },
  });

  return summary.text;
};

Periodic Consolidation

// Workflow for periodic memory consolidation
const consolidationWorkflow = createWorkflow({
  id: "memory-consolidation",
  inputSchema: z.object({ threadId: z.string() }),
  outputSchema: z.object({ consolidated: z.boolean() }),
})
  .then(
    createStep({
      id: "check-thread-size",
      execute: async ({ inputData, mastra }) => {
        const messages = await mastra?.storage?.getMessages({
          threadId: inputData.threadId,
          page: 1,
          perPage: 1,
        });

        // @ts-ignore - checking total count
        const totalCount = messages?.totalCount || 0;
        return { needsConsolidation: totalCount > 50 };
      },
    })
  )
  .branch([
    [
      async ({ inputData }) => inputData.needsConsolidation,
      createStep({
        id: "consolidate",
        execute: async ({ inputData, mastra }) => {
          await summarizeThread(inputData.threadId);
          // Optionally archive old messages
          return { consolidated: true };
        },
      }),
    ],
    [
      async () => true,
      createStep({
        id: "skip",
        execute: async () => ({ consolidated: false }),
      }),
    ],
  ])
  .commit();

Document Processing Pipeline

Chunking and Indexing Workflow

const indexDocumentWorkflow = createWorkflow({
  id: "index-document",
  inputSchema: z.object({
    documentId: z.string(),
    content: z.string(),
    metadata: z.record(z.any()),
  }),
  outputSchema: z.object({
    chunksIndexed: z.number(),
  }),
})
  .then(
    createStep({
      id: "chunk-document",
      execute: async ({ inputData }) => {
        // Split into chunks
        const chunks = chunkDocument(inputData.content, {
          maxChunkSize: 500,
          overlap: 50,
        });

        return {
          chunks: chunks.map((chunk, i) => ({
            id: `${inputData.documentId}-chunk-${i}`,
            content: chunk,
            metadata: {
              ...inputData.metadata,
              chunkIndex: i,
              documentId: inputData.documentId,
            },
          })),
        };
      },
    })
  )
  .then(
    createStep({
      id: "embed-chunks",
      execute: async ({ inputData }) => {
        const { embeddings } = await embedMany({
          model: openai.embedding("text-embedding-3-small"),
          values: inputData.chunks.map(c => c.content),
        });

        return {
          vectors: inputData.chunks.map((chunk, i) => ({
            id: chunk.id,
            vector: embeddings[i],
            metadata: chunk.metadata,
          })),
        };
      },
    })
  )
  .then(
    createStep({
      id: "store-vectors",
      execute: async ({ inputData, mastra }) => {
        await mastra?.vectors?.default.upsert({
          indexName: "documents",
          vectors: inputData.vectors,
        });

        return { chunksIndexed: inputData.vectors.length };
      },
    })
  )
  .commit();

Best Practices

1. Chunk Size Optimization

// Smaller chunks for precise retrieval
const preciseChunks = chunkDocument(content, { maxChunkSize: 200 });

// Larger chunks for more context
const contextualChunks = chunkDocument(content, { maxChunkSize: 1000 });

2. Metadata Enrichment

// Rich metadata enables better filtering
await vectorStore.upsert({
  vectors: [{
    id: "doc-1",
    vector: embedding,
    metadata: {
      content: text,
      source: "docs",
      category: "api-reference",
      createdAt: new Date().toISOString(),
      author: "engineering-team",
      version: "2.0",
    },
  }],
});

3. Cache Embeddings

const embeddingCache = new Map<string, number[]>();

async function getEmbedding(text: string): Promise<number[]> {
  const cacheKey = createHash("md5").update(text).digest("hex");

  if (embeddingCache.has(cacheKey)) {
    return embeddingCache.get(cacheKey)!;
  }

  const { embedding } = await embed({
    model: openai.embedding("text-embedding-3-small"),
    value: text,
  });

  embeddingCache.set(cacheKey, embedding);
  return embedding;
}

4. Thread Isolation

// Use unique thread IDs per conversation context
const threadId = `${userId}-${sessionId}`;

// Or per topic
const threadId = `${userId}-support-ticket-${ticketId}`;

5. Memory Limits

// Prevent runaway context costs
const response = await agent.generate(message, {
  memory: {
    thread: threadId,
    resource: userId,
    options: {
      maxMessages: 20,
      maxTokens: 8000,
      summarizeAfter: 15, // Auto-summarize after 15 messages
    },
  },
});

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive developer toolkit for building AI agents and workflows with Mastra v1 Beta and Hono servers. It includes scaffolding scripts, code templates, and detailed documentation on best practices for agent orchestration and data flow. All external dependencies are from well-known technology organizations and the code patterns align with standard software development practices.

  • Socket16d

    1 alert: gptAnomaly

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    22/22 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 99a8797. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 8 months ago
compatibility
Node.js 22.13.0+ required for v1 Beta
Other metadata
metadata
{
  "author": "agent-skills",
  "version": "1.0",
  "type": "utility",
  "mode": "assistive",
  "domain": "development"
}

README badge

README badge for jwynia/agent-skills/mastra-hono