# When to Use Different Emitter Types in Agent-Lightning: A Complete Guide to Observability

> Master agent-lightning emitter types for effective observability. Learn when to use emit_reward, emit_message, emit_object, and more for RL training, debugging, and tracing.

- Repository: [Microsoft/agent-lightning](https://github.com/microsoft/agent-lightning)
- Tags: how-to-guide
- Published: 2026-04-01

---

**Agent-lightning provides six specialized emitter types—`emit_reward`, `emit_message`, `emit_object`, `emit_exception`, `emit_annotation`, and `operation`—each designed to capture specific telemetry data as OpenTelemetry spans with distinct semantic conventions for RL training, debugging, and workflow tracing.**

Agent-lightning ships a lightweight **emitter** API that lets you record custom OpenTelemetry spans directly from your agent code. Understanding when to use different emitter types in agent-lightning ensures that reinforcement learning signals, debug logs, and exception traces follow consistent semantic conventions that downstream dashboards and reward-model trainers can reliably consume. This guide maps each emitter to its intended use case based on the actual implementation in the Microsoft agent-lightning repository.

## Choosing the Right Emitter for Your Use Case

Each emitter helper in agent-lightning wraps the active tracer to produce a span with a well-known semantic convention. Select the emitter that matches the data you need to capture.

### emit_reward: Recording Scalar and Multi-Dimensional Rewards

Use **`emit_reward`** when you need to report reinforcement-learning signals or any scalar feedback that a training loop will consume. According to the source code in [`agentlightning/emitter/reward.py#L148`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/reward.py#L148), this emitter creates a span of type `AGL_ANNOTATION` and stores either a numeric reward or a dictionary of multi-dimensional rewards.

- **Typical use**: Intermediate or final rewards during tool execution or episode completion.
- **Key feature**: Automatically links to the surrounding operation span, if any.
- **Data format**: Float or dict with optional `primary_key` for multi-objective rewards.

### emit_message: Debug Logging and Status Updates

Use **`emit_message`** for high-level status messages that you want to keep in the same telemetry store as your spans. This emitter, implemented in [`agentlightning/emitter/message.py#L15`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/message.py#L15), creates a span of type `AGL_MESSAGE` containing a free-form string plus optional attributes.

- **Typical use**: Debugging tool invocations, logging step transitions, or communicating agent state.
- **Key feature**: Lightweight string-based logging integrated with the OpenTelemetry pipeline.

### emit_object: Serializing Arbitrary Python Objects

Use **`emit_object`** when you need to capture model outputs, configurations, or any data structure useful for post-hoc inspection. As defined in [`agentlightning/emitter/object.py#L17`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/object.py#L17), this emitter creates a span of type `AGL_OBJECT` that serializes the object via `repr()`.

- **Typical use**: Storing structured model outputs, API responses, or agent configurations.
- **Key feature**: Automatically flattens nested dictionaries into dotted OpenTelemetry keys using `sanitize_attributes`.

### emit_exception: Capturing Non-Fatal Errors

Use **`emit_exception`** to report unexpected errors that you do not want to raise to the caller. The implementation in [`agentlightning/emitter/exception.py#L15`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/exception.py#L15) creates a span of type `AGL_EXCEPTION` marked with `exception.type`, `exception.message`, and `exception.stacktrace`.

- **Typical use**: Non-fatal failures in tool calls, fallback handling, or graceful degradation paths.
- **Key feature**: Records full traceback information without interrupting execution flow.

### emit_annotation: Generic Custom Metadata

Use **`emit_annotation`** when you need a custom data payload that does not fit the reward or message model. This generic emitter, located in [`agentlightning/emitter/annotation.py#L35`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/annotation.py#L35), produces an `AGL_ANNOTATION` span storing a free-form dictionary of key-value pairs.

- **Typical use**: Tagging spans with experiment metadata, model versions, or custom metrics.
- **Key feature**: Most flexible payload structure for domain-specific attributes.

### operation: Grouping Related Work into Logical Spans

Use the **`operation`** decorator or context manager to group a series of emitter calls, inputs, and outputs under a single parent span. Implemented in [`agentlightning/emitter/annotation.py#L281`](https://github.com/microsoft/agent-lightning/blob/main/agentlightning/emitter/annotation.py#L281), this creates an `AGL_OPERATION` span that lives for the duration of a logical block.

- **Typical use**: Wrapping a full tool invocation, multi-step workflow, or high-level agent step.
- **Key features**: Supports `set_input()`, `set_output()`, and automatic attribute flattening via `flatten_attributes`.

## Architecture: How Emitters Flow Through the Tracer

Understanding the internal flow helps you decide when to set `propagate=False` or add custom attributes.

1. **Tracer Resolution** – All emitter functions call `agentlightning.tracer.base.get_active_tracer()` to retrieve the currently bound tracer (usually an `OtelTracer`). If `propagate=False` is passed, a `DummyTracer` creates a local span that never reaches exporters.
2. **Semantic Conventions** – Each emitter uses constants from `agentlightning.semconv` (e.g., `AGL_MESSAGE`, `AGL_EXCEPTION`) ensuring downstream consumers can query spans by standardized names.
3. **Attribute Processing** – Helper utilities `flatten_attributes` and `sanitize_attributes` in `agentlightning.utils.otel` convert nested dictionaries into dotted OpenTelemetry keys (e.g., `meta.some_key`), allowing rich metadata without manual preprocessing.

## Code Examples

### Emitting Scalar and Multi-Dimensional Rewards

```python
from agentlightning import emit_reward

# Simple scalar reward

emit_reward(0.85)

# Multi-dimensional reward with a primary key

emit_reward({"task_success": 1.0, "efficiency": 0.7}, primary_key="task_success")

```

### Linking Rewards to Previous Spans

```python
from agentlightning import emit_reward
from agentlightning.utils.otel import make_link_attributes, make_tag_attributes

# Link the reward to a previously emitted response span

link_attrs = make_link_attributes({"gen_ai.response.id": "resp-123"})
emit_reward(0.5, attributes=link_attrs)

# Tag the reward span with custom tags

tag_attrs = make_tag_attributes(["fast", "reliable"])
emit_reward(0.7, attributes=tag_attrs)

```

### Logging Debug Messages

```python
from agentlightning import emit_message

emit_message("Starting tool call", attributes={"tool": "search"})

```

### Capturing Arbitrary Objects

```python
from agentlightning import emit_object

model_output = {"answer": "42", "confidence": 0.96}
emit_object(model_output, attributes={"step": "postprocess"})

```

### Reporting Exceptions Without Raising

```python
from agentlightning import emit_exception

try:
    risky_operation()
except Exception as exc:
    emit_exception(exc)  # Records stacktrace in a span

    # continue execution or fallback

```

### Grouping Work with the Operation Decorator

```python
from agentlightning import operation, emit_reward, emit_message

@operation(category="tool", tool_name="search")
def run_search(query: str):
    emit_message(f"Executing search for '{query}'")
    results = external_search_api(query)
    emit_reward(0.9, attributes={"result_count": len(results)})
    return results

```

### Using Operation as a Context Manager

```python
from agentlightning import operation

with operation(user_id=123) as op:
    op.set_input(data="input payload")
    # ... work ...

    op.set_output(result="output payload")

```

## Summary

- **Use `emit_reward`** for numerical RL feedback and multi-dimensional reward signals.
- **Use `emit_message`** for lightweight string-based debug logging and status updates.
- **Use `emit_object`** to serialize complex Python structures like model outputs or configs.
- **Use `emit_exception`** to capture non-fatal errors with full stacktraces without raising.
- **Use `emit_annotation`** for custom key-value metadata that does not fit other categories.
- **Use `operation`** to group related emitter calls into a single parent span with inputs and outputs.

## Frequently Asked Questions

### When should I use emit_reward versus emit_annotation?

**Use `emit_reward`** when the data represents a scalar or multi-dimensional reinforcement learning signal that a training loop will consume, as it enforces numeric or dictionary structures with optional `primary_key` semantics. **Use `emit_annotation`** for generic experiment metadata or custom metrics that do not represent reward signals, since it accepts any free-form dictionary without semantic constraints.

### How do I prevent spans from reaching external exporters during unit tests?

Pass `propagate=False` to any emitter function. This triggers the use of a `DummyTracer` that creates local spans only, preventing telemetry from hitting OTLP endpoints or console exporters while still allowing you to verify span creation in memory.

### Can I link an emitter span to a previous operation or response?

Yes. Import `make_link_attributes` from `agentlightning.utils.otel` and pass the resulting attributes to any emitter. This creates explicit span links using OpenTelemetry's linking mechanism, allowing you to correlate rewards or messages with prior generations or tool calls.

### What is the difference between emit_object and emit_message?

**`emit_message`** stores a string payload in an `AGL_MESSAGE` span and is optimized for human-readable debug text. **`emit_object`** stores the `repr()` of an arbitrary Python object in an `AGL_OBJECT` span, making it suitable for capturing structured data like dictionaries or class instances that require post-hoc analysis.