For the complete documentation index, see llms.txt. This page is also available as Markdown.

Mastra

Record Mastra agent runs, import existing trace exports, and replay with recorded conversation context

The Kitaru Mastra adapter wraps an existing Mastra Agent and records each non-streaming generate() call as a Kitaru session. Mastra still runs the agent and Kitaru returns the native Mastra result unchanged.

To bring in runs already recorded by Mastra, use Import existing Mastra traces. Importing an export does not require the original run to have used KitaruAgent.

Install

pnpm add @zenml-io/kitaru-mastra @mastra/core@1.64.0
npm install @zenml-io/kitaru-mastra @mastra/core@1.64.0

The adapter includes the framework-neutral @zenml-io/kitaru TypeScript package as a dependency.

Wrap an agent

Create your Mastra agent as usual, then pass it to KitaruAgent:

import { Agent } from "@mastra/core/agent";
import { KitaruAgent } from "@zenml-io/kitaru-mastra";

const agent = new Agent({
  id: "support-agent",
  name: "Support agent",
  instructions: "Answer support requests using the available tools.",
  model: "openai/gpt-5-mini",
  tools,
});

const recordedAgent = new KitaruAgent(agent, {
  agentId: process.env.KITARU_AGENT_ID!,
  agentVersionId: process.env.KITARU_AGENT_VERSION_ID,
  requestedModelId: "openai/gpt-5-mini",
  allowedReplayModels: ["openai/gpt-5-mini", "openai/gpt-5"],
  resolveModel: (modelId) => modelRegistry[modelId],
});

const result = await recordedAgent.generate(messages, options);
console.log(result.text);

Configure the adapter subprocess with KITARU_API_URL and either the worker-provided KITARU_API_TOKEN or KITARU_API_KEY. A separate Node management driver can use createKitaruClient() to reuse kitaru login without exporting a token. The wrapper calls the existing agent's public generate() method. It does not recreate tools, inspect private agent fields, install model middleware, or replace the returned result.

requestedModelId is the Kitaru model identifier for the normal run. allowedReplayModels limits which replay model overrides the process will accept. When a replay selects another allowed model, resolveModel turns its Kitaru identifier into a Mastra model configuration. If no replay can change the model, resolveModel can be omitted.

What Kitaru records

Each call creates isolated recording state and:

  1. Creates a Kitaru session and an in-progress root node.

  2. Records each completed Mastra step through the public onStepFinish callback.

  3. Writes one LLM node followed by that step's local tool children.

  4. Completes the same root node and session after Mastra succeeds, or records the failure when the run raises.

Each LLM node records the requested Kitaru model, the model and provider reported by Mastra, token usage, finish information, and provider metadata. Kitaru stores cost only when you provide a costCalculator; it does not calculate model prices on the server.

Step nodes do not record model inputs because Mastra repeats the full prompt and message history in each provider request. Step outputs include the finish reason, text, tool calls, tool results, tripwire details, and warnings. Tool inputs are the arguments requested by the model, before a tool schema applies defaults or coercion.

Recording uses bounded JSON conversion. Credential-shaped keys such as authorization, token, secret, password, api_key, apikey, and cookie are replaced with [redacted], and oversized or unsupported values are truncated or marked. This is a safety net, not a sensitive-data classifier. Do not put secrets or unnecessary personal data in prompts, tool inputs, tool outputs, or provider metadata.

The recorded node order reflects completed Mastra callbacks. It does not prove provider-side start order or wall-clock order among concurrent operations.

Preserve configured callbacks

Mastra's per-run hooks replace configured hooks. When the agent already has callbacks that must still run, pass them explicitly as configuredOnStepFinish, configuredBeforeToolCall, and configuredAfterToolCall in the KitaruAgent options. During replay, Kitaru evaluates the tool policy first. A passthrough call then runs the configured hook followed by the caller's per-run hook; a mocked call runs neither user tool hook. Kitaru records a step before it calls configured and per-run onStepFinish callbacks.

The wrapper does not inspect getConfiguredToolHooks(). Configured callbacks that are not passed explicitly cannot be preserved when replay replaces the corresponding per-run hook. Mastra also merges per-run model settings with configured defaults, so Kitaru can replace supplied keys but cannot remove configured keys it cannot inspect.

Replay behavior

A replay runs the same compiled command again. When the Kitaru worker sets KITARU_REPLAY_ID, the wrapper fetches the replay configuration and applies supported overrides through public per-run Mastra options and tool hooks. Application code does not need a separate replay branch.

The adapter can override:

  • The run input. A valid JSON value in KITARU_TASK_INPUTS takes precedence over the messages passed by the caller. If a worker input is too large for that environment variable, the adapter uses KITARU_TASK_ID to fetch the task specification instead. Outside a worker task, it uses the caller's messages.

  • System instructions. The override replaces per-run instructions and removes system messages from the effective input.

  • The model. A replacement must appear in allowedReplayModels and resolve through resolveModel.

  • Model settings: temperature, topP, topK, maxOutputTokens, presencePenalty, frequencyPenalty, seed, and stopSequences. Kitaru validates their types and bounds, then merges the changed settings with the caller's existing modelSettings.

  • Local tool behavior through the policies below.

Replay overrides take precedence over the legacy KITARU_OVERRIDE fallback; the two are not merged.

Tool policies

The Mastra adapter supports these tool policies for local executable tools:

Policy
Replay behavior

passthrough

Calls the original tool. Any network request, database write, message, payment, or other side effect happens for real.

static

Returns the configured value without calling the tool.

history

Looks up a previous result using the tool name and JSON inputs. On a miss, fail, passthrough, and error_result behavior is supported.

llm

Rejected before the tool executes; this policy is not supported in 0.1.0.

History matching uses the tool name and original JSON arguments. The Mastra importer preserves the raw exported arguments and result for this lookup, including arguments that a tool schema later coerces or fills with defaults. Other import formats or frameworks may serialize arguments differently, so matching logical calls alone does not guarantee a history match.

A completed history match replays its result, including null, without executing the live tool. A failed match throws ToolPolicyError with its stored error text and does not execute the live tool. Only a genuine miss follows the policy's on_miss behavior.

Before a replay starts, the adapter inventories configured tools, function-valued tools resolved from the run's requestContext, and per-run clientTools and toolsets. It rejects tools without a local execute function, approval-gated runs, sandboxed tools, and tool keys that Mastra would rename before exposing them to the model. Tools added only during execution and tools executed by a provider remain outside this preflight check and are not supported replay targets.

A tool-policy failure aborts the replay and records the session as failed. Replay forces toolCallConcurrency: 1 and aborts Mastra's generation loop as soon as a tool hook fails, so a later model step or sibling tool cannot continue after the policy failure. Kitaru does not recreate the original exception class or convert a matched failure into a native tool-error result.

Memory behavior

A supplied message array and recalled thread history are different inputs. An array contains only the messages the caller supplied; Mastra can still recall additional history when the invocation selects a memory thread.

For memory-dependent invocations, the adapter records a versioned conversation snapshot immediately before the first model step. Session inputs keep the supplied messages separately from the effective conversation, including its system messages and recalled history. The snapshot is tagged as memory-dependent; its message list is the combined effective input, not a separate recalled-only array. Replay uses that snapshot instead of recalling the thread again. This replays one invocation with its original context; it does not generate a new adaptive dialogue.

Replay removes per-run memory, threadId, resourceId, and savePerStep values, and removes Mastra's thread, resource, and internal memory keys from a copy of requestContext. It neither reads newer live history nor writes replay messages into the original thread. Default memory options remain unsupported because Mastra would merge them back after removal. Working memory, semantic recall, observational memory, and original invocations with user input processors or prepareStep are not replayable from these snapshots; they can add tools or change context beyond the first model step.

A missing, incomplete, or lossy snapshot produces an actionable unsupported-replay error before model execution. Record the invocation again with this adapter, or supply its complete recorded message array without live memory selectors. An explicit array without memory selectors continues to replay directly. Old recordings do not acquire missing history automatically. Their raw inputs do not identify whether memory was used, so removing memory settings from the replay entrypoint cannot establish that those inputs are complete. Record legacy memory-dependent invocations again before replaying them. Prompt and system-instruction overrides on conversation snapshots remain unsupported because replacing them can discard part of the recorded context; record a new invocation with the desired messages instead.

Structured output

Schema-only structured output is supported and remains available on the returned Mastra result:

A separate structuring model can be supplied in the per-run options:

Kitaru records each secondary provider attempt as a separate model node with its own model identity, bounded input and output, usage, and failure status. Mastra still validates the schema and returns its native result.object. A successful provider call can be followed by a schema validation failure, in which case the model node contains the returned text and the run is marked failed.

Replay model and model-setting overrides affect the parent agent only. The secondary model stays configured in the entrypoint and executes again against the parent's new output. Agent-default secondary models, useAgent: true, and errorStrategy: "warn" or "fallback" remain unsupported and are rejected before execution. Move a default secondary model into the per-run options and use the default strict error strategy.

Worker setup

Compile the agent into a Node command, register that command as the agent version's run specification, and run a worker that can execute it. Set KITARU_AGENT_ID in the run-spec environment. The worker supplies the task-scoped API URL and token, sets KITARU_TASK_ID, includes KITARU_TASK_INPUTS when it fits the environment boundary, and sets KITARU_REPLAY_ID for a replay.

The same entrypoint records a baseline session and executes replay jobs. Do not set replay environment variables manually around concurrent calls because environment variables are process-wide.

Supported boundary

The adapter supports:

  • Non-streaming Agent.generate() calls.

  • Local function tools, including function-valued tools resolved from the run's requestContext.

  • Per-run model, system-instruction, model-setting, and input overrides.

  • Passthrough, static, and same-adapter history tool policies.

  • Schema-only structured output and per-run secondary structuring models with strict validation.

It does not support streaming, workflows, subagents, MCP tools, provider-native tool replay, dynamic instructions, prepareStep, input processors, LLM tool policy, or TypeScript evaluators. prepareStep and input processors are rejected during replay because they can replace the model, prompt, or tools after policy preflight.

Import existing Mastra traces

Use the Mastra importer when the run already exists in Mastra observability. Each full trace becomes one Kitaru session, with its source inputs, outputs, span hierarchy, model usage, and tool arguments and results. Invocations from the same thread remain separate sessions; metadata.mastra.conversation_id retains their shared identity. The importer does not join a conversation into one synthetic invocation.

Export and register

Save the JSON response from Mastra's full GET /observability/traces/{traceId} endpoint, or serialize the storage getTrace({traceId}) result. The verified format is Mastra core 1.51.0: an object with traceId and a spans array containing the root and descendants. To import several selected traces, save a JSON array of those complete responses. Trace-list summaries, getTraceLight, raw exporter events, and OpenTelemetry payloads are not accepted substitutes.

The importer is not registered automatically under kitaru/. From a Kitaru source checkout containing plugins/packages/mastra-importer, upload the parser script once to your selected server:

These commands use the server selected by kitaru login. Pass --server URL to select another server explicitly. Registration creates the importer and its first version; reuse that importer for subsequent uploads. A worker must be running to parse the file.

Import for inspection or replay

Select an existing agent version that represents the exported run. For replay, its registered Node command must use the context-capable KitaruAgent described in Memory behavior, with the same callable tool names and compatible schemas. An importer preserves the trace; it does not supply runnable agent code.

For inspection and evaluation, import the file without replay parameters:

Default imports preserve the raw invocation input and set metadata.mastra.replay.eligible to false. The root input alone may omit recalled history, so do not treat it as complete replay context.

For a known history-only memory invocation, choose the following mode on the first import:

replay_context declares that the original agent used history-only memory, without working, semantic, or observational memory, custom input processors, or prepareStep. The export does not prove these configuration choices; use this mode only when you know them. It preserves the original invocation under supplied_messages and puts the initial full model messages, including system instructions and recalled history, in a versioned mastra_conversation_context snapshot. Missing or ambiguous context and unfinished spans make the snapshot incomplete; the adapter rejects it before model execution. Prompt and system-instruction overrides on these snapshots are unsupported.

List the imported sessions and inspect one before replaying:

Check metadata.mastra.replay.eligible and its reasons. Eligibility metadata is advisory, not a server-enforced ban on replay. For an eligible snapshot, create a replay with an existing evaluator and baseline tool history:

The worker calls the model again with the saved context. A matching tool call returns its recorded result without executing the live tool; an unmatched call fails. Use the returned job ID with kitaru job watch <job-id> to follow completion.

Identity and limits

Reimporting a trace skips the existing session rather than updating it. Keep source_namespace stable for one source deployment. It distinguishes deployments that might reuse trace IDs. Changing parameters alone does not upgrade a default import into a replay snapshot. If you already imported the trace without replay context, use a new explicit namespace to create a separate replay-ready copy.

The importer accepts selected files only; it does not fetch traces or live memory. It preserves usage reported by the export without counting generation totals twice, and imports monetary cost only when the source explicitly identifies USD. Missing usage and cost remain missing. Malformed traces produce isolated import failures while valid neighboring traces continue. See Importing sessions for import counts and failure inspection.

Runnable example

The Mastra support-triage example records a real Mastra agent, runs the compiled Node command through a job-scoped Kitaru worker, then replays it with prompt, instruction, model-setting, and history-policy overrides. Its side-effecting queueRefundReview tool is answered from history during replay, so the example's append-only outbox remains unchanged.

Use Node 22 and a running Kitaru API backed by PostgreSQL:

See the example README for the complete environment and validation steps.

Last updated

Was this helpful?