Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mortemlabs.com/llms.txt

Use this file to discover all available pages before exploring further.

Events are the individual observations that make up a trace. Each event records one discrete operation: an LLM call, a tool invocation, a Solana transaction, or any custom step you want to track. Together they form the chronological replay you see in the Mortem dashboard. You do not need to emit events manually for providers you have already wrapped — wrapOpenAI, wrapAnthropic, and the other wrapper methods handle that automatically. Emit events directly when you want to record steps that wrappers cannot see, such as planning phases, data fetches, or custom decision logic.

Begin an event

Call session.beginEvent(type, payload, options) to start recording an event. The method returns an EventBuilder that you use to complete or fail the event when the operation finishes.
const event = session.beginEvent("custom", { step: "planning" })

Parameters

type
EventType
required
The category of operation being recorded. Must be one of:
  • "llm_call" — a call to a language model
  • "tool_call" — a tool or function invocation
  • "solana_tx" — a Solana transaction sent or simulated
  • "x402_payment" — an x402 payment operation
  • "mcp_call" — a call through the Model Context Protocol
  • "custom" — any other step you want to capture
payload
JsonValue
A JSON-serializable value describing the operation at start time. You can update or replace this payload when completing the event. Defaults to null.
options
BeginEventOptions
Optional configuration for the event builder.

BeginEventOptions

options.id
string
Override the auto-generated event ID. Must be unique within the trace.
options.parentEventId
string | null
Link this event as a child of another event. If you call beginEvent inside eventBuilder.run(), the parent ID is set automatically from async context. Pass it explicitly when you need to express nesting outside of run().
options.startedAt
Date
Override the event start timestamp. Defaults to new Date() at the moment beginEvent is called.

Complete an event

Call eventBuilder.complete(options) when the operation succeeds:
const planning = session.beginEvent("custom", { step: "planning" })

planning.complete({
  payload: { step: "planning", result: "ready" },
})

CompleteEventOptions

payload
JsonValue
The final payload to record for the event. Replaces the payload passed to beginEvent if provided.
endedAt
Date
Override the event end timestamp. Defaults to new Date() at the time complete is called.
status
string
Override the completion status string. Defaults to "ok".

Fail an event

Call eventBuilder.fail(error, options) when the operation throws or produces an error:
try {
  const result = await callExternalApi()
  apiEvent.complete({ payload: { result } })
} catch (error) {
  apiEvent.fail(error)
  throw error
}

FailEventOptions

payload
JsonValue
Additional context to record alongside the error message. Optional.
endedAt
Date
Override the event end timestamp. Defaults to new Date() at the time fail is called.
Calling complete or fail more than once on the same EventBuilder is safe — subsequent calls are silently ignored. The event is finalized on the first call.

Wrap async work with eventBuilder.run()

Use eventBuilder.run(callback) to automatically complete or fail an event based on whether the callback resolves or throws. It also sets this event as the active parent in async context, so any events begun inside the callback are automatically linked as children.
const analysis = session.beginEvent("custom", { step: "market-analysis" })

const price = await analysis.run(async () => {
  // events begun here are automatically nested under `analysis`
  return fetchCurrentPrice("SOL/USDC")
})
If the callback throws, the event is marked as failed and the error is re-thrown so your own error handling is unaffected.

Nest events with parent/child relationships

You can nest events to represent hierarchical operations. Events begun inside eventBuilder.run() inherit the parent automatically. When working outside of run(), pass parentEventId explicitly:
const outerEvent = session.beginEvent("custom", { step: "execution" })

const innerEvent = session.beginEvent("tool_call", { toolName: "fetchPrice" }, {
  parentEventId: outerEvent.id,
})

innerEvent.complete({ payload: { toolName: "fetchPrice", output: 185.4 } })
outerEvent.complete({ payload: { step: "execution", result: "done" } })
The dashboard renders nested events as an indented timeline, making it easy to see which sub-operations contributed to each higher-level step.

Custom event example

Custom events are the most flexible type. Use them to mark any step in your agent’s logic that you want visible in the trace:
const session = await mortem.startSession({
  inputSummary: "Evaluate and submit a JUP swap",
})

await session.run(async () => {
  // Step 1: planning
  const planning = session.beginEvent("custom", { step: "planning" })
  planning.complete({ payload: { step: "planning", result: "ready" } })

  // Step 2: risk check
  const riskCheck = session.beginEvent("custom", { step: "risk-check" })
  const passed = await runRiskChecks()

  if (!passed) {
    riskCheck.complete({
      payload: { step: "risk-check", result: "rejected" },
      status: "skipped",
    })
    return
  }

  riskCheck.complete({ payload: { step: "risk-check", result: "passed" } })

  // Step 3: submit transaction (recorded automatically by wrapConnection)
  await connection.sendTransaction(swapTx)
})
Emit a custom event for every meaningful decision point in your agent — not just LLM calls. The trace timeline is most useful when it shows the full reasoning chain, including guard checks, market data fetches, and routing decisions.