Integration docs

view as markdown

Send Mastra agent traces to taleseal

Add an OtelExporter with a custom provider block to your Mastra instance; every agent run then emits current GenAI semantic-convention spans, and taleseal composes each run into a draft tale in your dashboard — private, no public URL, published only when you choose.

Prerequisites

Install

Shell
npm install @mastra/[email protected] @mastra/[email protected] @mastra/[email protected] [email protected] zod

Configure

The provider: { custom: { … } } shape is the 1.50.x form — the exporter README's older plain-object config (endpoint at the top level) is stale and fails type-checking:

TypeScript
import { Mastra } from "@mastra/core";
import { Observability } from "@mastra/observability";
import { OtelExporter } from "@mastra/otel-exporter";

export const exporter = new OtelExporter({
  provider: {
    custom: {
      endpoint: "https://taleseal.com/v1/traces",
      protocol: "http/protobuf",
      headers: { "x-api-key": process.env.TALESEAL_API_KEY ?? "" },
    },
  },
});

export const mastra = new Mastra({
  agents: { myAgent },
  observability: new Observability({
    configs: { otel: { serviceName: "my-agent", exporters: [exporter] } },
  }),
});

In a short-lived script, await exporter.flush() before exit or buffered spans never leave the process.

Fire a test run (no model API key needed)

MockLanguageModelV4 from ai/test scripts the model, so this proves the telemetry path without any provider credentials. Note: at @mastra/core 1.50.x, agent.generate() drives the model through doGenerate — script that, not doStream. Save as test-taleseal.ts and run it (bun test-taleseal.ts or npx tsx test-taleseal.ts) with TALESEAL_API_KEY set:

TypeScript
// test-taleseal.ts — a scripted tool-calling run on a mock model
import { Mastra } from "@mastra/core";
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { Observability } from "@mastra/observability";
import { OtelExporter } from "@mastra/otel-exporter";
import { MockLanguageModelV4 } from "ai/test";
import { z } from "zod";

const usage = {
  inputTokens: { total: 20, noCache: 20, cacheRead: undefined, cacheWrite: undefined },
  outputTokens: { total: 10, text: 10, reasoning: undefined },
};

const model = new MockLanguageModelV4({
  // an array scripts consecutive calls: first a tool call, then the final answer
  doGenerate: [
    {
      content: [{ type: "tool-call", toolCallId: "call-1", toolName: "getWeather", input: '{"city":"Sheffield"}' }],
      finishReason: { unified: "tool-calls", raw: undefined },
      usage,
      warnings: [],
    },
    {
      content: [{ type: "text", text: "Dry and bright in Sheffield." }],
      finishReason: { unified: "stop", raw: undefined },
      usage,
      warnings: [],
    },
  ],
});

const getWeather = createTool({
  id: "getWeather",
  description: "Look up the weather for a city",
  inputSchema: z.object({ city: z.string() }),
  execute: async ({ city }) => `Dry and bright in ${city}.`,
});

const weatherAgent = new Agent({
  id: "weather-agent",
  name: "weather-agent",
  instructions: "Answer questions about the weather.",
  model,
  tools: { getWeather },
});

const exporter = new OtelExporter({
  provider: {
    custom: {
      endpoint: "https://taleseal.com/v1/traces",
      protocol: "http/protobuf",
      headers: { "x-api-key": process.env.TALESEAL_API_KEY ?? "" },
    },
  },
});

export const mastra = new Mastra({
  agents: { weatherAgent },
  observability: new Observability({
    configs: { otel: { serviceName: "my-agent", exporters: [exporter] } },
  }),
});

await mastra.getAgent("weatherAgent").generate("What is the weather in Sheffield?");

// a short-lived script must flush, or buffered spans never leave the process
await exporter.flush();

Mastra warns about falling back to in-memory storage — harmless for this test. The run exports 10 spans in one batch: invoke_agent weather-agent, model steps, inferences and chunks, one chat span and execute_tool getWeather.

Verify

  1. Run the test script above; it exits after the flush.
  2. Ask taleseal what it received:
Shell
curl -s https://taleseal.com/v1/otlp/status -H "Authorization: Bearer $TALESEAL_API_KEY"
  1. The response lists your recent runs, newest first:
JSON
{
  "runs": [
    {
      "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
      "firstSeen": "2026-07-13T14:02:01Z",
      "lastSeen": "2026-07-13T14:02:05Z",
      "spans": 10,
      "inputTokens": 20,
      "outputTokens": 10,
      "errored": false,
      "state": "collecting",
      "title": null,
      "draftUrl": null
    }
  ],
  "generatedAt": "2026-07-13T14:02:11Z"
}

Success = a run with spans ≥ 10 and errored: false, within seconds of the flush. state: "collecting" means spans are being received and counted — the integration works; you do not need to wait for anything else.

  1. The draft tale appears once the run has been idle for about two minutes (plus up to a minute of sweep interval): poll step 2 until state is "finalised" and title is set; draftUrl points at the draft.
  2. Tell your human: the draft is in the dashboard at taleseal.com/dashboard. Drafts are private with no public URL; publishing is a deliberate act from the dashboard.

If it didn't work

SymptomCauseFix
Type errors on the exporter configThe plain-object config from older exporter docsUse the provider: { custom: { endpoint, protocol, headers } } shape (1.50.x)
Mock model throws Not implementedThe test scripted doStream, but agent.generate() at 1.50.x calls doGenerateScript doGenerate as an array, as in the test above
Run completes but status shows no runsObservability not passed to new Mastra, or no flushPass observability: to the constructor; await exporter.flush() before exit
Warning about in-memory storageNo storage adapter configuredHarmless for this test; configure storage for production separately
401 invalid or missing API keyTALESEAL_API_KEY unset or wrong, or the header renamedExport the env var; the header must be x-api-key
415 unsupported media type on exportprotocol set to something other than http/protobufUse protocol: "http/protobuf" (OTLP/JSON also works); gRPC is not supported
Spans counted but draft never appearsRun still inside the idle gap, or spans still tricklingFinalisation is (idle gap 120 s) + (sweep tick ≤ 60 s) after the last span

Integration overview · llms.txt · panic path: DELETE /v1/tales?runId=<trace id hex>