Trace eve agents (Vercel's eve framework, npm `eve`) with Maple: add one OpenTelemetry destination file under agent/instrumentation/ so each eve session is one Maple Agent Session with transcript, tool calls, subagent lanes, approvals and tokens. Covers eve dev, eve start, the channel audience that gates message content, and dropping the workflow engine's spans. Triggers on 'trace my eve agent', 'add Maple to eve', 'agent sessions for eve', 'OpenTelemetry for eve'.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
One eve session (wrun_...) = one Maple Agent Session. One user message = one turn = one trace rooted at invoke_agent <agent>, with agent.step spans holding a chat <model> span (model, provider, tokens, messages) and execute_tool <tool> spans (arguments, result, failures). Subagents nest inside the caller's trace and show as their own lanes.
eve owns the tracer provider and writes gen_ai.conversation.id (the root session id) on every agent span. You add a destination; you never start an SDK and never pass a conversation id.
Known gaps (tell the user, don't try to fix):
eve dev, message content is exported only for public conversations (Step 4).gen_ai.usage.cost on the agent.step span; Maple counts it in the session total (the per-model table has tokens, not cost).eve version in package.json / lockfile.
>= 0.62: this skill. Exercised on 0.62.0, 0.70.3, 0.72.1, 0.77.0 and 0.78.0 with the Step 2/3 files unchanged; every version produced complete sessions in Maple.0.62 to 0.71: each tool call is an agent.action span wrapping execute_tool. Maple shows the wrapper as a structure row and counts one tool call. Expected.< 0.62: telemetry lives in agent/instrumentation.ts (defineInstrumentation({ setup }) calling registerOTel), and eve ignores agent/instrumentation/ without an error: the Step 2 files export nothing (tested on 0.61.1). With experimental: { instrumentationProviders: true } in agent.ts they export, but the exportPolicy is ignored and every workflow span goes out. Don't use the flag: upgrade (npm install eve@latest), delete agent/instrumentation.ts (until then eve build fails with Found removed instrumentation file), remove the flag, then do Steps 2-3. Move every other exporter from the old setup into its own otelIntegration({ traceExporter }) file; serviceName becomes otel({ resource }), functionId becomes otel({ functionId }), events["step.started"] runtime context becomes otelIntegration({ runtimeContext }). See https://eve.dev/docs/observability/instrumentation-migration.recordInputs/recordOutputs were set (before 0.35 it recorded content by default); after the upgrade, content is recorded during eve dev and for public conversations (Step 4). The first eve dev after the upgrade deletes the local .eve/.workflow-data (incompatible storage layout), so open local dev sessions are lost.>= 24 at runtime; eve enforces it. bun or pnpm as the package manager is fine.agent/, or a workspace with agents/<name>/agent/. In a workspace add the files to each member's agent/instrumentation/ (not exercised; run Step 7 per member).agent/instrumentation/:
otel() file: add the resource attributes from Step 3 to it. Two otel() files are a boot error.otelIntegration() files (Honeycomb, Braintrust, Datadog...): keep them. Maple is one more file; each destination has its own exportPolicy.agent-runs.ts, local.ts: leave them.NodeSDK, registerOTel(, registerTelemetry(, @ai-sdk/otel, NodeTracerProvider. eve replaces all of them with otelIntegration(); don't add one, and tell the user if one exists.eve depends on ai. Use this skill, not maple-agent-tracing-vercel-ai-sdk.agent/skills/maple-agent-tracing-eve/ exists (an npx skills add run in an eve project installs into the eve agent's own skills, and skills-lock.json records it): it would ship with the agent as a runtime skill. Tell the user and remove both the directory and its skills-lock.json entry once setup is done, unless they want it there.agent.ts (model: "openai/gpt-…") runs through Vercel AI Gateway; a provider object (openai() from eve/models/openai, createOpenRouter(...), anthropic()) calls the provider directly. This decides the cost line in Step 7.https://ingest.maple.dev/v1/traces, EU https://ingest.eu.maple.dev/v1/traces. The exporter takes the full URL. Header authorization: Bearer <key>.MAPLE_TEST (ingest accepts and discards it) and tell the user to replace it with their key from Settings → Ingestion.maple_sk_ keys never go in browser code. Ingest keys are write-only..env.local for eve dev and eve start: put MAPLE_INGEST_KEY=<key> there (check it is gitignored). Deployments read the host's environment variables.eve build works without the key.Bearer undefined. When the key is missing, log one warning and export disableInstrumentation() so the agent runs normally (Step 2 does this).ingest_unauthorized with a key you trust usually means the key belongs to the other region: try the other endpoint.npm install @vercel/otelUse the repo's package manager. Create agent/instrumentation/maple.ts:
import { OTLPHttpProtoTraceExporter } from "@vercel/otel"
import { disableInstrumentation } from "eve/instrumentation"
import { otelIntegration } from "eve/instrumentation/otel"
// A missing key disables export; it never stops the agent.
const mapleKey = process.env.MAPLE_INGEST_KEY
if (!mapleKey) console.warn("MAPLE_INGEST_KEY is not set; Maple telemetry export is disabled")
export default mapleKey
? otelIntegration({
traceExporter: new OTLPHttpProtoTraceExporter({
url: "https://ingest.maple.dev/v1/traces",
headers: { authorization: `Bearer ${mapleKey}` },
}),
// Keep the spans of a conversation; drop the workflow engine's own
exportPolicy: { span: ({ attributes }) => ({ emit: "gen_ai.conversation.id" in attributes }) },
})
: disableInstrumentation()traceExporter in its own batch processor (5 s delay, flushes on shutdown). Don't wrap it in a BatchSpanProcessor yourself.exportPolicy is required in practice. eve's second scope, workflow, is the durable-execution engine: workflow.*, world.*, step.*, hook.resume, queue.publish, including one workflow.stream.flush span per streamed chunk. In two test runs it was 1,031 of 1,060 and 1,794 of 1,829 spans, all in traces of their own with no gen_ai.conversation.id.gen_ai.conversation.id at start. Per eve's docs the id is also on third-party instrumentation spans created during model, tool and memory execution; spans outside those (for example agent.channel.request with traceChannelRequests: true) are dropped by this policy.exportPolicy and tell them the volume.Without settings, service.name is the package.json name. Create agent/instrumentation/otel.ts, or extend the existing otel() file:
import { otel } from "eve/instrumentation/otel"
export default otel({
resource: {
"service.name": "support-agent",
"deployment.environment.name": process.env.VERCEL_ENV ?? "development",
},
})VERCEL_ENV on Vercel).gen_ai.agent.name is the package.json name for the root agent and the directory name for a declared subagent (agent/subagents/<name>/). They are the lane names in Maple.eve attaches content to spans only when the conversation's audience allows it. The channel assigns the audience once, when the session starts:
| Runs as | Audience | Content exported |
|---|---|---|
eve dev | any | yes |
preview / production (eve start, Vercel) | public | yes |
| preview / production | private / unknown | no |
gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions, gen_ai.tool.definitions, gen_ai.tool.call.arguments, gen_ai.tool.call.result, the session title and the turn labels.eveChannel): authenticated user/service/runtime principals are private, anonymous callers unknown. Slack: public channels public; DMs, group DMs and private channels private.otel({ tracePolicy }) cannot widen this. Tested on eve start with a private caller and a policy returning { emit: true, recordInputs: true, recordOutputs: true }: the spans say agent.trace.content.input=true and still carry no content. The policy only narrows.eveChannel({ auth: [...], audience: "public" }) in agent/channels/eve.ts. The file replaces the whole default channel, so keep the auth list the project already uses. audience also takes a function; a custom channel has an audience() hook. See https://eve.dev/docs/channels/overview#audience.audience: "public". It is a data decision: it applies to every trace destination, including Vercel Agent Runs, not only Maple. Don't set it on your own.exportPolicy: [
{ span: ({ attributes }) => ({ emit: "gen_ai.conversation.id" in attributes }) },
{ span: () => ({ redact: true, inputs: true, outputs: true }) },
],gen_ai.tool.name are its file name under agent/tools/.error.type=ACTION_RESULT_FAILED, an exception event; Maple counts the tool call as failed. A returned { error } counts as success.execute_tool <subagent> and returns a task receipt within ~0.1 s; the child's invoke_agent <subagent> nests under it in the same trace. In Maple the lane's result is the receipt text ("Started task ..."), and the real result arrives through the framework tool eve__task_wait. Expected.eve__task_wait, bash, read_file...) are tool calls like any other.approval: always() and friends): eve adds an agent.approval span under the tool call. The turn stays open while it waits, so the wait counts toward the turn's and the tool call's duration. A denied call is a failed tool call with error.type=TOOL_EXECUTION_DENIED, which raises Maple's "Tool errors" check. Expected.invoke_agent spans repeat the turn total and agent.step repeats the call's usage under agent.usage.*. Maple counts the chat spans only; totals are not doubled.eve start, a SIGTERM right after a turn still delivered every span. Vercel deployments were not exercised.EVE_LOG_LEVEL=warn. A wrong key or region looks exactly like no traffic. Test the key directly:curl -s -w "%{http_code}\n" -X POST https://ingest.maple.dev/v1/traces \
-H "Authorization: Bearer <key>" \
-H "Content-Type: application/json" -d '{"resourceSpans":[]}'200 = valid. 401 prints the reason, including the right endpoint when the key is from the other region.
Run one real conversation: eve dev (or eve dev --no-ui plus a script on eve/client), 2+ messages in one session, one of them calling a tool; plus a second session. If the project has subagents or an approval-gated tool, exercise each once. Then check (Maple MCP list_agent_sessions filtered by service, get_agent_session_transcript, or the Agent Sessions page, ~30 s after the run):
MAPLE_INGEST_KEY is not set.wrun_...; the second session is separate; no trace:<id> sessions.chat <model> span has model, provider, input and output tokens. LLM calls = number of chat spans.workflow.*, world.*, step.*, hook.* or queue.* for the service (Maple MCP search_traces, or the Traces page).eve build && eve start) and send one message. Expect the session without content unless the audience is public; tell the user which it is.Without Maple access: the curl check in Step 6 returns 200, the startup log is clean, and a conversation ran. That shows delivery is possible, not that spans arrived; say so.
NodeSDK, registerOTel or registerTelemetry, and do not add @ai-sdk/otel, OpenLLMetry or OpenInference: eve already traces every model call.gen_ai.conversation.id or maple_ai.session.id yourself.otel() file.audience: "public" without the user's decision.MapleTechLabs/maple
27a645c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.