> ## 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.

# Record custom and manual events within a Mortem session

> Use session.beginEvent() to record LLM calls, tool calls, Solana transactions, and custom steps as timestamped events inside a trace.

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](/sdk/wrappers) 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.

```ts theme={null}
const event = session.beginEvent("custom", { step: "planning" })
```

### Parameters

<ParamField path="type" 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
</ParamField>

<ParamField path="payload" type="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`.
</ParamField>

<ParamField path="options" type="BeginEventOptions">
  Optional configuration for the event builder.
</ParamField>

### BeginEventOptions

<ParamField path="options.id" type="string">
  Override the auto-generated event ID. Must be unique within the trace.
</ParamField>

<ParamField path="options.parentEventId" type="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()`.
</ParamField>

<ParamField path="options.startedAt" type="Date">
  Override the event start timestamp. Defaults to `new Date()` at the moment `beginEvent` is called.
</ParamField>

## Complete an event

Call `eventBuilder.complete(options)` when the operation succeeds:

```ts theme={null}
const planning = session.beginEvent("custom", { step: "planning" })

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

### CompleteEventOptions

<ParamField path="payload" type="JsonValue">
  The final payload to record for the event. Replaces the payload passed to `beginEvent` if provided.
</ParamField>

<ParamField path="endedAt" type="Date">
  Override the event end timestamp. Defaults to `new Date()` at the time `complete` is called.
</ParamField>

<ParamField path="status" type="string">
  Override the completion status string. Defaults to `"ok"`.
</ParamField>

## Fail an event

Call `eventBuilder.fail(error, options)` when the operation throws or produces an error:

```ts theme={null}
try {
  const result = await callExternalApi()
  apiEvent.complete({ payload: { result } })
} catch (error) {
  apiEvent.fail(error)
  throw error
}
```

### FailEventOptions

<ParamField path="payload" type="JsonValue">
  Additional context to record alongside the error message. Optional.
</ParamField>

<ParamField path="endedAt" type="Date">
  Override the event end timestamp. Defaults to `new Date()` at the time `fail` is called.
</ParamField>

<Note>
  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.
</Note>

## 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.

```ts theme={null}
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:

```ts theme={null}
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:

```ts theme={null}
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)
})
```

<Tip>
  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.
</Tip>
