Agent frameworks
Caveman vs Mastra: token compression for Mastra agents
Keep Mastra and add Caveman Middleware: withCavemanMastra wraps your agent, shrinks large tool results before each model call, and keeps originals recoverable.
- 01Keep your Mastra agent
- 02Wrap it once
- 03Shrink tool results
- 04Recover originals on demand
Mastra is the framework your TypeScript agent runs on. Caveman Middleware is an adapter that wraps a Mastra agent and shrinks large tool results before each model call. Keep Mastra for agents, workflows and memory, and add Caveman where tools return more text than the next step needs.
What is Mastra?
Mastra is an open-source TypeScript framework for AI agents and applications. Its homepage calls it "the leading TypeScript agent framework", and the repo has about 28k GitHub stars. The core is Apache 2.0. Enterprise features live in ee/ folders under a source-available Mastra Enterprise License that is free for development and testing.
It covers a lot of ground in one package:
- Agents with instructions, tools built with
createTool(), and models picked with aprovider/modelstring. - Workflows, a graph-based engine with suspend and resume for human-in-the-loop steps.
- Memory, including conversation history, RAG retrieval and observational memory.
- Processors that run before and after the model, at each step of the loop.
- Evals and observability, plus Mastra Studio for building and testing agents locally.
- MCP server authoring.
The framework is free. Mastra's hosted platform has a $0 Starter tier (100K observability events and 24 CPU hours a month), a Teams tier at $250 a month, and custom Enterprise and self-hosted plans. Its homepage lists case studies from teams including Replit, Sanity, WorkOS and MongoDB.
Does Caveman work with Mastra?
Yes. @caveman-ai/middleware/mastra is a native adapter for @mastra/core >=1.65 <2. You pass your existing agent to withCavemanMastra and call generate or stream on the result, same as before. This is the docs example:
import { withCavemanMastra } from '@caveman-ai/middleware/mastra';
export async function run({ agent, messages, runtime, scope }) {
if (runtime.mode !== "off") await runtime.ready(); // ready() throws in off mode
const wrapped = withCavemanMastra(agent, { runtime, scope });
return wrapped.generate(messages, { maxSteps: 4 });
}Under the hood it is built on Mastra's own processor system. On each call the wrapper adds a Caveman input processor in front of the processors you configured, keeps your prepareStep, and registers a caveman_retrieve tool built with Mastra's createTool. Your Agent config is not mutated. Right before the provider call, eligible tool-result text in the outbound prompt is swapped for a shorter version from a local Caveman runtime. Your memory and message history keep the originals. If the model needs a detail that got cut, it calls caveman_retrieve and gets the exact original back.
Tool results that failed or were denied are left alone, as are user messages, system prompts and images.
For a feel of the size change: the Caveman docs send a 400-line worker log as a tool result, and the runtime counted 8,399 tokens before and 177 to 187 after across sixteen runs. Those are inferred o200k_base counts on one synthetic log, not a bill.
Mastra vs Caveman: feature comparison
| Mastra | Caveman Middleware | |
|---|---|---|
| What it is | TypeScript framework for agents and workflows | Adapter that shrinks tool results before model calls |
| License | Apache 2.0 core, enterprise features source-available | MIT client and adapters, BSL local runtime |
| Calls the model | Yes, through its model router | No, Mastra still makes the call |
| Agents, workflows, memory | Yes | Uses yours |
| Context trimming | TokenLimiter drops older messages, ToolCallFilter drops tool calls | Leaves history as is |
| Shrinks large tool results | Not built in | Yes, on the outbound copy |
| Getting cut content back | Dropped content is not sent | caveman_retrieve returns the original |
| Observability | Built-in traces, evals, Studio | Per-call decision reports |
| Hosted option | Mastra platform | None needed, runtime runs locally |
| Maturity | Established | Alpha, 0.1.0-alpha.2 |
When should you use Mastra alone?
Use Mastra alone when your tools return short results. There is nothing to compress in a status code or a three-field JSON object, and Caveman will report no_candidate or not_smaller and send the original.
It is also the right call when TokenLimiter and ToolCallFilter already keep your context in check and answers stay good. Dropping old tool calls is cheap and simple. You only need something smarter when the tool results you keep are the expensive part.
One Mastra-specific note: when a call uses structured output, forces a specific tool, or limits activeTools so caveman_retrieve is excluded, the adapter does not offer the recovery tool for that call. Without recovery the runtime shortens nothing, so that call goes out unchanged. Plan your trial on a normal text-output agent first.
When does adding Caveman pay off?
It pays off when a Mastra agent reads files, calls APIs that return big JSON, runs shell commands or pulls documents. Each of those results is resent on every later step. A 20k-token API response fetched at step 2 of a 10-step run rides along on eight more model calls.
Caveman shortens it on each resend. After the first cut it resends the exact same shortened bytes, reported as reused, so the provider's cached prefix stays byte identical.
It stacks with Mastra's processors. TokenLimiter decides how much history goes out. Caveman shrinks the tool results inside what is left, at the last moment before the provider call.
If the Caveman runtime is unreachable, the call goes through with the original input. After three straight failures the adapter skips the runtime for 30 seconds. The agent keeps running either way.
How to add Caveman to a Mastra agent
npm install @caveman-ai/middleware @caveman-ai/sdk- Start the runtime with
CAVEMAN_MODE=compress caveman start. - Wrap the agent:
withCavemanMastra(agent, { runtime, scope }). - Log
onReportand look forappliedandreused.
The Mastra switch guide walks through each step with the runtime setup, scope and rollback. The supported frameworks page has the version gates, and how to measure agent cost shows how to judge the result on finished tasks.
FAQ
Does Caveman replace Mastra? No. Mastra still runs your agents, workflows, memory and model calls. Caveman only shortens tool-result text in the copy of the request sent to the provider.
Does it work with agent.stream()?
Yes. withCavemanMastra wraps both generate and stream.
Will it change what Mastra stores in memory? No. The shortened text exists only in the outbound request. Your stored messages and tool results keep their original content.
Where does the scope come from?
You pass one, or a function that builds it from Mastra's requestContext. Derive it from your authenticated user and conversation, never from model input.
Does Caveman need Mastra's hosted platform? No. The adapter and runtime run in your own environment, next to Mastra wherever you deploy it.
Sources
Try it on work you know
Keep your stack. Add the adapter.
Wrap the client or framework you already call, run one agent task you can check, and compare the tokens sent.
Open the middleware docs Explore comparisons ↗