# Emitter (Observability)

> Location within the framework `Hive-agent-framework/emitter`.

An emitter is a core functionality of the framework that allows you to see what is happening under the hood.

## Standalone usage

The following examples demonstrate how `Emitter` concept works.

### Basic Usage

```ts
import { Emitter, EventMeta } from "Hive-agent-framework/emitter/emitter";

// Get the root emitter or create your own
const root = Emitter.root;

root.match("*.*", async (data: unknown, event: EventMeta) => {
  console.log(`Received event '${event.path}' with data ${JSON.stringify(data)}`);
});

await root.emit("start", { id: 123 });
await root.emit("end", { id: 123 });
```

*Source: examples/emitter/base.ts*

> \[!NOTE]
>
> You can create your own emitter by initiating the `Emitter` class, but typically it's better to use or fork the root one (as can be seen in the following examples).

### Advanced

```ts
import { Emitter, EventMeta, Callback } from "Hive-agent-framework/emitter/emitter";

// Define events in advanced
interface Events {
  start: Callback<{ id: number }>;
  update: Callback<{ id: number; data: string }>;
}

// Create emitter with a type support
const emitter = Emitter.root.child<Events>({
  namespace: ["Hive", "demo"],
  creator: {}, // typically a class
  context: {}, // custom data (propagates to the event's context property)
  groupId: undefined, // optional id for grouping common events (propagates to the event's groupId property)
  trace: undefined, // data related to identity what emitted what and which context (internally used by framework's components)
});

// Listen for "start" event
emitter.on("start", async (data, event: EventMeta) => {
  console.log(`Received ${event.name} event with id "${data.id}"`);
});

// Listen for "update" event
emitter.on("update", async (data, event: EventMeta) => {
  console.log(`Received ${event.name}' with id "${data.id}" and data ${data.data}`);
});

await emitter.emit("start", { id: 123 });
await emitter.emit("update", { id: 123, data: "Hello Hive!" });
```

*Source: examples/emitter/advanced.ts*

> \[!NOTE]
>
> Because we created the `Emitter` instance directly emitted events will not be propagated to the `root` which may or may not be desired. The `piping` concept is explained later on.

### Event Matching

```ts
import { Callback, Emitter } from "Hive-agent-framework/emitter/emitter";
import { BaseLLM } from "Hive-agent-framework/llms/base";

interface Events {
  update: Callback<{ data: string }>;
}

const emitter = new Emitter<Events>({
  namespace: ["app"],
});

// Match events by a concrete name (strictly typed)
emitter.on("update", async (data, event) => {});

// Match all events emitted directly on the instance (not nested)
emitter.match("*", async (data, event) => {});

// Match all events (included nested)
emitter.match("*.*", async (data, event) => {});

// Match events by providing a filter function
emitter.match(
  (event) => event.creator instanceof BaseLLM,
  async (data, event) => {},
);

// Match events by regex
emitter.match(/watsonx/, async (data, event) => {});
```

*Source: examples/emitter/matchers.ts*

### Event Piping

```ts
import { Emitter, EventMeta } from "Hive-agent-framework/emitter/emitter";

const first = new Emitter({
  namespace: ["app"],
});

first.match("*.*", (data: unknown, event: EventMeta) => {
  console.log(
    `'first' has retrieved the following event ${event.path}, isDirect: ${event.source === first}`,
  );
});

const second = new Emitter({
  namespace: ["app", "llm"],
});
second.match("*.*", (data: unknown, event: EventMeta) => {
  console.log(
    `'second' has retrieved the following event '${event.path}', isDirect: ${event.source === second}`,
  );
});

// Propagate all events from the 'second' emitter to the 'first' emitter
const unpipe = second.pipe(first);

await first.emit("a", {});
await second.emit("b", {});

console.log("Unpipe");
unpipe();

await first.emit("c", {});
await second.emit("d", {});
```

*Source: examples/emitter/piping.ts*

## Framework Usage

Typically, you consume out-of-the-box modules that use the `Emitter` concept on your behalf.

## Agent usage

```ts
import { HiveAgent } from "Hive-agent-framework/agents/Hive/agent";
import { UnconstrainedMemory } from "Hive-agent-framework/memory/unconstrainedMemory";
import { OllamaChatLLM } from "Hive-agent-framework/adapters/ollama/chat";

const agent = new HiveAgent({
  llm: new OllamaChatLLM(),
  memory: new UnconstrainedMemory(),
  tools: [],
});

// Matching events on the instance level
agent.emitter.match("*.*", (data, event) => {});

await agent
  .run({
    prompt: "Hello agent!",
  })
  .observe((emitter) => {
    // Matching events on the execution (run) level
    emitter.match("*.*", (data, event) => {
      console.info(`RUN LOG: received event '${event.path}'`);
    });
  });
```

*Source: examples/emitter/agentMatchers.ts*

> \[!IMPORTANT]
>
> The `observe` method is also supported on `Tools` and `LLMs`.

> \[!TIP]
>
> The more complex agentic example can be found here.

> \[!TIP]
>
> To verify if a given class instance has one, check for the presence of the `emitter` property.
