AI
Tutorial Framework Agent TypeScript Mastra: Workflow dan Evals
Oktober 202613 menit baca

Mastra adalah framework TypeScript open-source untuk AI agent, workflow, memory, evals dan tracing, dibuat oleh tim di balik Gatsby. Versi 1.0.0 dari @mastra/core terbit di npm pada 20 Januari 2026, dan para pendirinya menyebut pemakaian production di perusahaan seperti Replit, PayPal dan Sanity. Lini 1.x sering merilis versi baru, jadi pin versinya di production.
Ya. Mastra berlisensi Apache 2.0 untuk semua yang berada di luar direktori bernama ee di repository-nya. Direktori ee tersebut, termasuk sebagian kode auth dan Agent Builder, memakai lisensi enterprise terpisah, jadi periksa file LICENSE sebelum mengandalkannya.
Agent adalah loop tool calling: model memutuskan tool mana yang dipanggil dan kapan berhenti, cocok untuk tugas yang terbuka. Workflow adalah rangkaian step bertipe yang disambung dengan then, parallel, branch dan foreach, dan kode Anda yang menentukan urutannya. Workflow bisa di-suspend dan di-resume dari storage, sehingga menjadi tempat yang tepat untuk aturan approval dan step human-in-the-loop.
Install @mastra/evals lalu panggil runEvals dari Vitest atau test runner ESM lain dengan dataset, target yang diambil lewat mastra.getAgent, dan quick check seperti calledTool, noToolErrors dan maxToolCalls. Check yang dicantumkan di gates harus bernilai rata-rata 1.0 atau verdict-nya failed. Quick check tidak butuh LLM, jadi cepat dan gratis dijalankan di setiap commit.
Pakai AI SDK saja ketika fiturnya berupa antarmuka chat dengan beberapa tool dan pekerjaan selesai ketika stream selesai. Pakai Mastra ketika Anda juga butuh workflow yang awet dan bisa di-suspend menunggu seseorang, evals bawaan, dan Studio lokal. Mastra bergantung pada package provider AI SDK dan panduan Next.js-nya melakukan streaming lewat AI SDK UI, jadi keduanya bisa digabung.

Ringkasan Utama
Mastra adalah framework agent TypeScript berlisensi Apache 2.0 yang mencapai versi 1.0 pada Januari 2026. Gunakan agent-nya untuk tool calling yang terbuka, workflow-nya untuk langkah deterministik yang bisa di-suspend dan di-resume dari storage, dan evals quick check di Vitest untuk menjaga pemakaian tool di CI. Simpan aturan approval di kode workflow, bukan di prompt.
Bayangkan tim procurement di sebuah ERP yang dibangun dengan Next.js. Buyer membuat purchase order sepanjang hari, dan ada yang ingin asisten yang membaca riwayat pengiriman supplier sebelum PO sampai ke manajer. Prototipe pertama biasanya satu agent dengan prompt panjang yang menjelaskan kapan harus approve. Di demo itu jalan, lalu auditor bertanya baris kode mana yang memutuskan order 48 juta rupiah boleh melewati manajer, dan jawaban jujurnya adalah satu paragraf bahasa Inggris yang dibaca model secara berbeda di hari yang buruk.
Tutorial framework agent TypeScript Mastra ini membangun fitur yang sama dengan cara sebaliknya memakai Mastra 1.x: tool yang bertipe, agent yang hanya mendeskripsikan risiko, workflow yang memegang aturan approval dan berhenti menunggu manusia, evals yang berjalan di Vitest, dan Studio lokal. Setiap nama API diambil dari dokumentasi Mastra terbaru dan metadata npm, dan bagian terakhir membandingkan Mastra dengan Vercel AI SDK dan OpenAI Agents SDK untuk tim yang menulis TypeScript dari ujung ke ujung. Opsi yang Python-first seperti LangGraph dan CrewAI dibahas terpisah.
Mastra adalah framework TypeScript open-source dari tim di balik Gatsby, perusahaan Y Combinator angkatan W25. Versi 1.0.0 dari @mastra/core terbit di npm pada 20 Januari 2026, dan postingan peluncuran para pendirinya menyebut lebih dari 300.000 download npm per minggu, 19,4 ribu GitHub star, dan lisensi Apache 2.0. Sejak itu lini 1.x bergerak cepat: @mastra/core berada di versi 1.73.0 di npm saat tulisan ini dibuat. Empat fakta berikut membentuk semua bagian selanjutnya:
Pemisahan agent dan workflow adalah keputusan desain utamanya. Dokumentasinya menulis dengan lugas: pakai agent ketika langkahnya belum diketahui di awal, dan workflow ketika prosesnya sudah terdefinisi. Approval purchase order sebagian besar termasuk jenis kedua, dengan satu penilaian di tengahnya. Perhatikan juga bahwa @mastra/core mencantumkan Node.js 22.13.0 atau lebih baru di field engines dan menerima zod 3.25 atau zod 4 sebagai peer dependency.
Mastra bisa berjalan sebagai server sendiri, tetapi untuk ERP yang sudah punya codebase Next.js App Router, meng-import-nya langsung ke route handler adalah jalur terpendek. Perintah init dari CLI menambahkan folder src/mastra berisi contoh agent, tool dan file index ke project yang sudah ada.
# Inside an existing Next.js App Router project.
# @mastra/core 1.x declares engines: node >= 22.13.0.
npx mastra@latest init # creates src/mastra/{index.ts,agents,tools}
npm install @mastra/libsql @mastra/evals zod
npm install -D vitest
# .env
# The model router reads the provider key from the model string:
# any 'openai/...' model id needs OPENAI_API_KEY, no provider import.
OPENAI_API_KEY=sk-...Instance Mastra adalah registry-nya. Agent, workflow dan storage didaftarkan sekali, dan semua bagian lain mencarinya lewat key sehingga memakai logger, storage dan tracing yang sama. Dua baris di file ini mencegah sebagian besar kebingungan di awal: flag di config Next.js, dan path storage yang absolut.
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Documented for Next.js on Vercel: keep Mastra out of the bundle.
serverExternalPackages: ['@mastra/*'],
};
export default nextConfig;
// src/mastra/index.ts
import { Mastra } from '@mastra/core';
import { LibSQLStore } from '@mastra/libsql';
import { procurementAgent } from './agents/procurement-agent';
import { purchaseOrderWorkflow } from './workflows/purchase-order';
export const mastra = new Mastra({
agents: { procurementAgent },
workflows: { purchaseOrderWorkflow },
// Suspended workflow snapshots and eval scores are written here.
// Absolute path on purpose: `next dev` and `mastra dev` start in
// different working directories, and a relative file: URL gives each
// process its own database. LibSQL needs a real filesystem, so this
// suits a VPS or container, not a serverless function.
storage: new LibSQLStore({ url: 'file:/srv/erp-app/data/mastra.db' }),
});LibSQLStore menulis file lokal, dan dokumentasi deployment Mastra meminta untuk menghapusnya sebelum deploy ke platform serverless. Di VPS atau container Docker dengan volume yang di-mount, ini berfungsi, tetapi workflow yang di-suspend hanya seawet file tersebut. Kalau volume-nya tidak persisten, satu redeploy diam-diam membuang semua PO yang sedang menunggu manajer. Pindah ke store berbasis database, misalnya package @mastra/pg, sebelum approval sungguhan bergantung padanya.
Tool Mastra dibuat dengan createTool dan memiliki id, description, inputSchema Zod, outputSchema opsional dan fungsi execute. Dokumentasinya menegaskan bahwa execute hanya punya satu signature, inputData lebih dulu lalu execution context, dan bahwa definisi tool berupa object biasa gagal dieksekusi tanpa pesan error. Context tersebut membawa requestContext, tracing context dan abort signal.
// src/mastra/tools/supplier-tool.ts
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
import { db } from '../../lib/db';
export const getSupplierHistory = createTool({
id: 'get-supplier-history',
description:
'Look up one supplier by code, for example SUP-0193: open purchase ' +
'orders, late deliveries in the last 12 months and payment terms.',
inputSchema: z.object({
supplierCode: z.string().regex(/^SUP-\d{4}$/).describe('Supplier code'),
}),
outputSchema: z.object({
found: z.boolean(),
name: z.string().optional(),
openPoCount: z.number().optional(),
lateDeliveries12m: z.number().optional(),
paymentTermsDays: z.number().optional(),
}),
// Checked before execute() runs. On failure the tool RETURNS an error
// object to the model instead of throwing, so the run keeps going.
requestContextSchema: z.object({ tenantId: z.string() }),
// One signature only: execute(inputData, context).
execute: async ({ supplierCode }, { requestContext }) => {
// The tenant comes from the server session via RequestContext,
// never from an argument the model was free to invent.
const tenantId = requestContext?.get('tenantId');
const supplier = await db.supplier.findFirst({
where: { code: supplierCode, tenantId },
select: { name: true, openPoCount: true, lateDeliveries12m: true, paymentTermsDays: true },
});
// "Not found" is an answer the agent can explain, not an exception.
return supplier ? { found: true, ...supplier } : { found: false };
},
});RequestContext adalah cara fakta dari sisi server sampai ke tool tanpa melewati model. Route handler mengisi tenantId dari session, lalu tool membacanya. Model hanya memilih supplierCode, yang dibatasi regex agar sesuai format ERP sendiri. Mendeklarasikan requestContextSchema pada tool menambah pengecekan sebelum execute berjalan. Kalau gagal, agent melempar MastraError sebelum ada panggilan model, tetapi tool mengembalikan object error yang bisa dibaca model, jadi uji kedua jalur itu dan jangan mengasumsikan perilakunya sama.
Agent-nya sendiri kecil. Instruksinya menyebut apa yang boleh dilakukan dan, yang lebih penting, apa yang tidak boleh: agent mendeskripsikan risiko supplier dan tidak pernah meng-approve apa pun. Tool dikirim sebagai object, dan key dari object itu lebih penting daripada kelihatannya.
// src/mastra/agents/procurement-agent.ts
import { Agent } from '@mastra/core/agent';
import { getSupplierHistory } from '../tools/supplier-tool';
export const procurementAgent = new Agent({
id: 'procurement-agent',
name: 'Procurement Agent',
description: 'Assesses supplier risk for a purchase order',
instructions: `You assess supplier risk for purchase orders in an ERP.
Always call getSupplierHistory before giving an opinion.
You never approve or reject a purchase order. You only describe risk.`,
// provider/model string, resolved by Mastra's model router.
model: 'openai/gpt-5-mini',
// The OBJECT KEY is the toolName the model, the traces and the eval
// checks see. Here that is 'getSupplierHistory', not the tool's id.
tools: { getSupplierHistory },
// One step = one model call. A risk check needs a lookup and an answer.
defaultOptions: { maxSteps: 4 },
hooks: {
// Runs before every tool call from any source, including MCP tools.
// Return { proceed: false, output } here to block a call by policy.
beforeToolCall: ({ toolName, input }) => {
console.info('[procurement-agent] tool call', toolName, input);
},
},
});Memanggil agent.generate mengembalikan text, tool call, tool result, step dan pemakaian token; agent.stream menyediakan textStream ditambah promise yang selesai ketika run berakhir. Mengirim structuredOutput dengan schema Zod mengembalikan object tervalidasi di response.object alih-alih prosa, dan begitulah workflow di bawah memakai agent ini. Hooks memberi satu tempat untuk mencatat atau memblokir tool call: mengembalikan proceed false dari beforeToolCall akan melewati panggilan itu dan memberikan output Anda kepada agent sebagai tool result.
Nama tool yang dilihat model, trace dan eval check adalah key di object tools, bukan id yang dikirim ke createTool. Dengan tools berisi getSupplierHistory, stream melaporkan getSupplierHistory, dan checks.calledTool harus memakai string yang sama. Pilih satu konvensi, key sama dengan id atau key dalam camelCase, lalu pakai di semua agent.
Di sinilah aturan approval keluar dari prompt. Workflow menjalankan pengecekan budget dengan kode biasa dan penilaian supplier lewat agent secara paralel, membentuk ulang kedua hasil dengan map, lalu melakukan branch. Risiko rendah, masih dalam budget dan di bawah 50 juta rupiah langsung di-approve otomatis; selain itu di-suspend menunggu manajer.
// src/mastra/workflows/purchase-order.ts
import { createStep, createWorkflow } from '@mastra/core/workflows';
import { z } from 'zod';
import { getRemainingBudget } from '../../lib/budget';
const AUTO_APPROVE_LIMIT_IDR = 50_000_000;
const PoInput = z.object({
poNumber: z.string(),
supplierCode: z.string(),
costCentre: z.string(),
amountIdr: z.number().positive(),
});
// Plain code: no model involved, so it gives the same answer every run.
const checkBudget = createStep({
id: 'check-budget',
inputSchema: PoInput,
outputSchema: z.object({ withinBudget: z.boolean() }),
execute: async ({ inputData, requestContext }) => {
const remaining = await getRemainingBudget(
requestContext.get('tenantId'),
inputData.costCentre,
);
return { withinBudget: inputData.amountIdr <= remaining };
},
});
const Risk = z.object({
risk: z.enum(['low', 'medium', 'high']),
reason: z.string(),
});
// The one step that asks a model, and it must answer in a schema.
const assessSupplier = createStep({
id: 'assess-supplier',
inputSchema: PoInput,
outputSchema: Risk,
retries: 2, // a provider blip retries this step, not the whole run
execute: async ({ inputData, mastra, requestContext }) => {
const agent = mastra.getAgent('procurementAgent');
const res = await agent.generate(
'Assess supplier ' + inputData.supplierCode + ' for ' + inputData.poNumber +
' worth IDR ' + inputData.amountIdr,
{ structuredOutput: { schema: Risk }, requestContext },
);
return res.object;
},
});
const Routing = z.object({
poNumber: z.string(),
amountIdr: z.number(),
withinBudget: z.boolean(),
risk: z.enum(['low', 'medium', 'high']),
reason: z.string(),
});
const Decision = z.object({
status: z.enum(['approved', 'rejected']),
decidedBy: z.string(),
});
const autoApprove = createStep({
id: 'auto-approve',
inputSchema: Routing,
outputSchema: Decision,
execute: async () => ({ status: 'approved' as const, decidedBy: 'policy:auto' }),
});
const managerApproval = createStep({
id: 'manager-approval',
inputSchema: Routing,
outputSchema: Decision,
suspendSchema: z.object({ poNumber: z.string(), reason: z.string() }),
resumeSchema: z.object({ approved: z.boolean(), managerId: z.string() }),
execute: async ({ inputData, resumeData, suspend }) => {
if (!resumeData) {
// A snapshot goes to storage. The process may restart, or be
// redeployed, before the manager clicks anything.
return await suspend({ poNumber: inputData.poNumber, reason: inputData.reason });
}
return {
status: resumeData.approved ? ('approved' as const) : ('rejected' as const),
decidedBy: resumeData.managerId,
};
},
});
const canAutoApprove = (r: z.infer<typeof Routing>) =>
r.withinBudget && r.risk === 'low' && r.amountIdr <= AUTO_APPROVE_LIMIT_IDR;
export const purchaseOrderWorkflow = createWorkflow({
id: 'purchase-order-approval',
inputSchema: PoInput,
// A branch's output is keyed by the step id that actually ran.
outputSchema: z.object({
'auto-approve': Decision.optional(),
'manager-approval': Decision.optional(),
}),
})
.parallel([checkBudget, assessSupplier])
// Parallel output is keyed by step id; reshape it for the branch.
.map(async ({ inputData, getInitData }) => {
const po = getInitData<z.infer<typeof PoInput>>();
return {
poNumber: po.poNumber,
amountIdr: po.amountIdr,
withinBudget: inputData['check-budget'].withinBudget,
risk: inputData['assess-supplier'].risk,
reason: inputData['assess-supplier'].reason,
};
})
// Conditions are evaluated in order. The model's opinion is one input
// to the rule; the rule itself is code you can read in review.
.branch([
[async ({ inputData }) => canAutoApprove(inputData), autoApprove],
[async ({ inputData }) => !canAutoApprove(inputData), managerApproval],
])
.commit();Tiga detail di file ini mengikuti langsung dokumentasinya. Output parallel dan branch berupa object dengan key id step yang berjalan, karena itu map membaca check-budget dan assess-supplier, dan outputSchema workflow mencantumkan kedua step branch sebagai opsional. Setiap step di dalam branch harus memakai input dan output schema yang sama. Kondisi branch dievaluasi berurutan dan hanya kecocokan pertama yang dijalankan. Batas 50 juta hanyalah nilai contoh untuk ilustrasi, bukan rekomendasi.
Suspend inilah yang membuatnya menjadi workflow, bukan request yang berjalan lama. Step manager-approval memanggil suspend, run kembali dengan status suspended, dan snapshot ditulis ke storage. Beberapa jam kemudian, request HTTP yang berbeda membuat ulang run dari runId-nya dan memanggil resume dengan data yang harus cocok dengan resumeSchema. Hasilnya adalah discriminated union berdasarkan status, dengan nilai success, failed, suspended, tripwire dan paused, sehingga setiap route menangani setiap kasus secara eksplisit.
// src/app/api/po/route.ts (a buyer submits a purchase order)
import { RequestContext } from '@mastra/core/request-context';
import { mastra } from '@/mastra';
import { getSession } from '@/lib/auth';
import { savePoRunId } from '@/lib/po';
export const runtime = 'nodejs';
export async function POST(req: Request) {
const session = await getSession(req);
if (!session) return new Response('Unauthorized', { status: 401 });
const requestContext = new RequestContext<{ tenantId: string }>();
requestContext.set('tenantId', session.tenantId);
const po = await req.json();
const runId = crypto.randomUUID();
const run = await mastra.getWorkflow('purchaseOrderWorkflow').createRun({ runId });
const result = await run.start({ inputData: po, requestContext });
if (result.status === 'suspended') {
// Keep the runId on the PO row. The approve button needs it later.
await savePoRunId(po.poNumber, runId);
return Response.json({ state: 'awaiting-manager', poNumber: po.poNumber });
}
if (result.status === 'success') return Response.json({ state: 'decided', ...result.result });
return Response.json({ state: result.status }, { status: 500 });
}
// src/app/api/po/approve/route.ts (the manager answers, hours later)
export async function POST(req: Request) {
const session = await getSession(req);
if (!session?.roles.includes('po-approver')) return new Response('Forbidden', { status: 403 });
const { runId, approved } = await req.json();
const requestContext = new RequestContext<{ tenantId: string }>();
requestContext.set('tenantId', session.tenantId);
const run = await mastra.getWorkflow('purchaseOrderWorkflow').createRun({ runId });
const result = await run.resume({
step: 'manager-approval',
// managerId comes from the session, not the request body.
resumeData: { approved, managerId: session.userId },
requestContext,
});
return Response.json({ state: result.status });
}Scorer Mastra ada di package @mastra/evals. Quick check adalah micro-scorer deterministik yang tidak butuh model: includes, excludes, matches, calledTool, didNotCall, toolOrder, maxToolCalls, usedNoTools dan noToolErrors. runEvals menjalankan dataset melalui agent atau workflow, dan semua yang dicantumkan di gates harus bernilai rata-rata 1.0 atau verdict-nya failed, persis sinyal biner yang dibutuhkan job CI.
// src/mastra/procurement-agent.eval.test.ts
import { describe, it, expect } from 'vitest';
import { runEvals } from '@mastra/core/evals';
import { checks } from '@mastra/evals/checks';
import { RequestContext } from '@mastra/core/request-context';
import { mastra } from './index';
const tenant = new RequestContext<{ tenantId: string }>();
tenant.set('tenantId', 'tenant-eval'); // seeded test tenant, not production
describe('procurement agent', () => {
it('looks the supplier up before judging risk', async () => {
const result = await runEvals({
// From the registry, not a direct import: a bare import has no
// Mastra instance, so scores are not persisted and registry
// lookups inside steps and tools fail.
target: mastra.getAgent('procurementAgent'),
data: [
{ input: 'Assess supplier SUP-0193 for PO-2026-0815 worth IDR 42,000,000.', requestContext: tenant },
{ input: 'Is SUP-0007 risky for a rush order of packaging film?', requestContext: tenant },
{ input: 'Assess SUP-9999 for PO-2026-0816.', requestContext: tenant }, // unknown supplier
],
// Gates must average 1.0 or the verdict is 'failed'. No LLM, no cost.
gates: [
// The tools-object KEY, not the createTool id.
checks.calledTool('getSupplierHistory'),
checks.noToolErrors(),
checks.maxToolCalls(3),
],
});
expect(result.verdict).toBe('passed');
expect(result.summary.totalItems).toBe(3);
});
});Ada dua jebakan yang terdokumentasi tetapi mudah terlewat. Ambil target lewat mastra.getAgent alih-alih meng-import agent-nya, kalau tidak score tidak tersimpan dan lookup registry di dalam step gagal. Lalu setiap instance calledTool memakai id yang sama, check-called-tool, sehingga dua calledTool check dalam satu run bertabrakan di map scores; pakai toolOrder ketika sebuah test peduli lebih dari satu tool. Untuk penilaian kualitas yang tidak bisa dilakukan kode, scorer berbasis model seperti answer relevancy dan faithfulness bisa dimasukkan ke array yang sama, dan live scorer bisa dipasang di agent dengan sampling rate sehingga sebagian traffic production dinilai di background.
Menjalankan mastra dev akan membuka Studio di localhost port 4111, dengan REST API terdokumentasi di path swagger-ui pada port yang sama. Ini cara tercepat melihat apa yang sebenarnya dilakukan agent dan workflow sebelum menghubungkannya ke UI ERP. Urutan praktis untuk fitur PO:
Studio berjalan sebagai proses terpisah dari next dev, dan itulah alasan URL storage di file index dibuat absolut: dengan path relatif, setiap proses mencari file dari working directory masing-masing, dan run yang dimulai di aplikasi Next.js tidak pernah muncul di Studio. Studio juga bisa di-deploy dengan autentikasi untuk satu tim, tetapi untuk project ERP dengan satu developer, instance lokal biasanya sudah cukup.
Ketiganya TypeScript-native dan ketiganya memakai Zod untuk schema tool, jadi pilihannya soal seberapa banyak framework yang Anda inginkan di sekitar loop model. Mastra sendiri bergantung pada package provider AI SDK, dan panduan Next.js-nya melakukan streaming lewat AI SDK UI, jadi dua yang pertama lebih tepat disebut lapisan daripada pesaing.
| Aspek | Mastra 1.x | Vercel AI SDK 7 | OpenAI Agents SDK (TypeScript) |
|---|---|---|---|
| Loop agent | Class Agent dengan generate dan stream, maxSteps dan stopWhen, subagent diekspos sebagai tool | Class ToolLoopAgent dengan stopWhen dan prepareStep untuk kontrol loop | Agent ditambah run, dengan handoff antar agent dan guardrail |
| Workflow deterministik | Engine bawaan: then, parallel, branch, foreach, suspend dan resume yang disimpan ke storage | Didokumentasikan sebagai pola TypeScript biasa: sequential, parallel, routing, orchestrator-worker | Orkestrasi di kode atau lewat handoff; human-in-the-loop dan session terdokumentasi |
| Evals | Scorer @mastra/evals, quick check, runEvals dengan gate, live scoring dengan sampling | Tidak termasuk di dokumentasi agent yang dikutip di sini; pasangkan dengan tool eval eksternal | Panduan testing dan tracing bawaan; pasangkan dengan tool eval eksternal untuk scoring |
| UI dev lokal | Studio di port 4111 dengan chat, graph workflow, trace dan time travel | Tidak dibahas di dokumentasi yang dikutip; lapisan UI-nya AI SDK UI untuk aplikasi Anda sendiri | Tidak dibahas di dokumentasi yang dikutip; trace dikirim ke tracing exporter yang dikonfigurasi |
| Model | String provider garis miring model lewat model router, lebih dari 40 provider menurut README | Package provider atau string model, banyak provider | Model OpenAI lebih dulu, provider lain lewat integrasi AI SDK yang terdokumentasi |
| Package dan lisensi | @mastra/core 1.73.0, Apache 2.0 di luar direktori ee, Node.js 22.13 atau lebih baru | ai 7.0.x di npm, Apache 2.0 | @openai/agents 0.18.0, MIT, zod 4 wajib sebagai peer dependency |
Aturan yang masuk akal: pilih AI SDK saja ketika fiturnya berupa kotak chat dengan beberapa tool dan request selesai ketika stream selesai. Pilih OpenAI Agents SDK ketika produknya dibangun di sekitar model OpenAI dan handoff multi-agent. Pilih Mastra ketika fiturnya butuh proses yang hidup lebih lama dari satu request, misalnya approval yang menunggu seseorang, sekaligus evals dan UI lokal dari package yang sama. Harganya adalah dependency yang lebih besar dan ritme rilis yang cepat, jadi pin versi dan baca changelog sebelum upgrade.
Package yang dipakai di tutorial ini: @mastra/core untuk agent, tool, workflow dan runEvals; @mastra/libsql untuk storage lokal; @mastra/evals untuk quick check dan scorer; dan mastra, CLI yang menjalankan init dan dev. Untuk UI chat di Next.js, panduan Mastra menambahkan @mastra/ai-sdk dengan hook useChat dari AI SDK.
Aturan yang layak dibawa ke fitur agent apa pun di ERP: biarkan agent mendeskripsikan dan biarkan kode yang memutuskan. Mastra membuat pemisahan itu murah di TypeScript, dengan tool Zod yang mengambil data tenant dari RequestContext, workflow yang kondisi branch-nya berupa fungsi yang bisa dibaca reviewer, step suspend yang menunggu selama manajer belum menjawab, dan gate Vitest yang menggagalkan build ketika agent berhenti memanggil tool-nya. Kalau prompt adalah satu-satunya tempat sebuah aturan bisnis ditulis, aturan itu seharusnya pindah ke step workflow.
Sumber