# Instrument custom analytics

> Declare bounded typed events in an Agent or Channel and emit metrics without granting tenant code analytics storage access.

## Before you begin {#before-you-begin}

Define the operational question first. Dimensions describe bounded categories used to filter or group; metrics are numeric values to aggregate. Do not put secrets, prompts, raw user content, unbounded identifiers, session ids, or request ids into custom dimensions.

## Steps {#steps}

1. Declare the event with a stable namespaced id and version:

   ```ts
   import { agent, analyticsEvent } from "@constal/sdk";

   const ticketResolved = analyticsEvent({
     id: "support.ticket-resolved",
     version: "1",
     dimensions: ["queue", "resolution"],
     metrics: ["duration_ms", "messages"],
   } as const);
   ```

2. Register it in the executable definition with `analytics: [ticketResolved]`.
3. Emit it from Agent code with `ctx.analytics.emit()` or Channel code with `context.analytics.emit()`:

   ```ts
   ctx.analytics.emit(ticketResolved, {
     dimensions: { queue: "billing", resolution: "answered" },
     metrics: { duration_ms: 842, messages: 3 },
   });
   ```

4. Increment the event version when its meaning changes. Keep old and new definitions distinguishable during migration.
5. Deploy the package. The runtime records emission intent with the durable invocation; tenant executables receive no direct archive, projection, or query authority.

## Verify {#verify}

Run one controlled input and query with `filters.custom_event_id` set to the declaration id. Confirm the returned `custom_event_version`, dimensions, and metrics. Repeat a durable invocation or replay scenario and verify event identity remains suitable for downstream deduplication.

## Next steps {#next-steps}

Use [Query and export analytics](/docs/analytics/query-and-export.md) to retrieve the event. Review [SDK Policies and analytics](/docs/sdk/policies-and-analytics.md) for the runtime API and event validation limits.
