# How to Define a Custom RuntimeEvent in Apache Maka

> Learn how to define a custom RuntimeEvent in Apache Maka. Declare a payload interface, extend RuntimeEvent, and emit using RuntimeEventStore append for custom event handling.

- Repository: [The Apache Software Foundation/maka](https://github.com/apache/maka)
- Tags: how-to-guide
- Published: 2026-09-09

---

**To define a custom RuntimeEvent in Apache Maka, you must declare a new payload interface with a unique literal `kind` property, extend the `RuntimeEvent` discriminated union in [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts), and emit the event via the `RuntimeEventStore.append()` method.**

Apache Maka treats every meaningful fact of agent execution as a **RuntimeEvent**, using a discriminated union pattern where the `kind` field determines the payload shape. When you define a custom RuntimeEvent in Apache Maka, you extend this type-safe event log to capture domain-specific facts while maintaining full TypeScript exhaustiveness checking across read models and storage layers.

## Step 1: Declare the Custom Event Payload Interface

Begin by defining a new interface in [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) following the established pattern of built-in types like `RuntimeEventSystemNoteContent` (lines 41-46) or `RuntimeEventErrorContent` (lines 48-56). The interface must include a literal `kind` property that is unique across all events.

```typescript
// packages/core/src/runtime-event.ts
export interface RuntimeEventMyCustomContent {
  kind: 'my_custom';                 // <-- unique literal string
  /** Any structured data you want to capture */
  meta: Record<string, unknown>;
  /** Optional human-readable description */
  description?: string;
}

```

The `kind` property acts as the discriminant tag. TypeScript uses this literal to narrow the union type when processing events in switch statements or if-else chains.

## Step 2: Extend the RuntimeEvent Discriminated Union

Locate the `RuntimeEvent` type definition near the bottom of [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) and append your new interface to the union. This step ensures that all consumers—persistence stores, UI projections, and read models—recognize the new shape through exhaustive type checking.

```typescript
// packages/core/src/runtime-event.ts
export type RuntimeEvent =
    | RuntimeEventTextContent
    | RuntimeEventThinkingContent
    | RuntimeEventFunctionCallContent
    | RuntimeEventFunctionResponseContent
    | RuntimeEventSystemNoteContent
    | RuntimeEventErrorContent
    // <-- add your custom payload here
    | RuntimeEventMyCustomContent;

```

Failing to add the interface to this union will result in TypeScript errors when attempting to store or project the event, as the type system will not recognize the payload as a valid runtime event.

## Step 3: Persist and Emit the Custom Event

Emit the event by calling `RuntimeEventStore.append()` from your application logic. The SQLite-based store at [`packages/storage/src/sqlite-runtime-store.ts`](https://github.com/apache/maka/blob/main/packages/storage/src/sqlite-runtime-store.ts) stores events in a generic JSON column, so no database schema migration is required for new event kinds.

```typescript
import { runtimeEventStore } from '@maka/storage';
import { RuntimeEventMyCustomContent } from '@maka/core';

const myEvent: RuntimeEventMyCustomContent = {
  kind: 'my_custom',
  meta: { taskId: '1234', status: 'queued' },
  description: 'Task queued by the scheduler',
};

await runtimeEventStore.append(myEvent);

```

The `runtimeEventStore` serializes the payload and appends it to the event log, making it available for asynchronous projections and historical queries.

## Step 4: Consume the Event in Read Models (Optional)

Most runtime-event projections operate on the generic `RuntimeEvent` type, but you can add specialized handling logic by checking the `kind` discriminator. Update mapper files such as [`packages/runtime/src/session-event-runtime-mapper.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/session-event-runtime-mapper.ts) or custom `RuntimeEventReadModel` implementations to handle your specific event type.

```typescript
// packages/runtime/src/runtime-event-read-model.ts
if (event.kind === 'my_custom') {
  // TypeScript narrows `event` to RuntimeEventMyCustomContent here
  console.log('Processing custom event:', event.meta.taskId);
}

```

## Key Files Involved in Custom RuntimeEvent Definition

| File | Role |
|------|------|
| [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) | Central definition of all payload interfaces and the discriminated union |
| [`packages/storage/src/sqlite-runtime-store.ts`](https://github.com/apache/maka/blob/main/packages/storage/src/sqlite-runtime-store.ts) | Persists events using a JSON column; requires no schema changes for new kinds |
| [`packages/runtime/src/runtime-event-read-model.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/runtime-event-read-model.ts) | Example read-model projection that consumes runtime events |

## Summary

- **Declare the payload**: Create an interface in [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) with a unique literal `kind` property following the pattern of `RuntimeEventErrorContent` or `RuntimeEventSystemNoteContent`.
- **Extend the union**: Add the interface to the `RuntimeEvent` discriminated union type to enable type-safe consumption across the framework.
- **Emit via store**: Use `RuntimeEventStore.append()` from `@maka/storage` to persist events without database migrations.
- **Project selectively**: Handle custom events in read models by discriminating on the `kind` field in mappers or custom projections.

## Frequently Asked Questions

### Where is the RuntimeEvent union type defined?

The `RuntimeEvent` union type is defined at the bottom of [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts). This file contains all payload interfaces and the central union that discriminates between them based on the `kind` property.

### Do I need to migrate the database schema for new event types?

No. The default SQLite storage implementation in [`packages/storage/src/sqlite-runtime-store.ts`](https://github.com/apache/maka/blob/main/packages/storage/src/sqlite-runtime-store.ts) uses a generic JSON column to store event payloads. Because the schema stores arbitrary JSON, adding new event kinds requires no DDL changes. However, if you implement a custom storage backend with strict column typing, you must ensure the schema accommodates the new payload shape.

### How do I ensure my custom event kind is unique?

Choose a descriptive literal string for the `kind` property that follows the existing snake_case convention (e.g., `my_custom_feature`). Verify uniqueness by checking the existing payload interfaces in [`packages/core/src/runtime-event.ts`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) to ensure no other interface declares the same literal. TypeScript will also flag overlapping types if the discriminant is not unique.

### Can I emit custom events from any part of the application?

Yes. Any code that can import `runtimeEventStore` from `@maka/storage` and your custom event type from `@maka/core` can emit events. Ensure the event object conforms to your declared interface and call `await runtimeEventStore.append(event)` to persist it to the runtime log.