GuidesEmitted attributes

Emitted attributes

Your traces already say what your agent did. Emitted attributes let it also say what happened for the business: who it was talking to, whether it had to escalate, why it handed off, and what the customer thought. Your agent sets a handful of neens.* attributes on a span, and Neens lifts them onto the conversation as first-class business signal — badged Emitted, because the agent asserted them about itself.

This is the answer to business facts that have no place in a standard trace. There is no OpenTelemetry attribute for “this case was escalated to a human”, or “the customer gave it three stars”. So Neens documents a small, neens.-prefixed convention you can set from any tracing SDK, and reads it back as measures you can chart, alert on, and promote to a KPI.

At a glance

WhatA neens.* span-attribute namespace your agent sets to state business facts about a turn.
HowSet the attributes on any span with your tracing SDK; Neens lifts them at ingest. No extra endpoint to call.
Where it landsOn the session — an end_user_id you can filter and erase by, plus nested metadata a custom measure can read.
ProvenanceEmitted — your agent’s own claim. Distinct from Measured (your system of record) and Inferred (an LLM).
SetupNone beyond sending the attributes. They work the moment your agent emits them.

The neens.* namespace

Set any of these as span attributes. Values are scalars (string, number, boolean); a list or object is ignored. Strings are stored up to 200 characters. You can set them on any span in the trace — the agent’s root span is the natural home.

AttributeTypeMeaning
neens.end_user_idstringYour own id for the human on the other side of the conversation. Powers repeat-contact analysis and is the key end-user data is erased by.
neens.escalatedbooleanWhether this case was escalated (e.g. to a human queue). Set false too, so “not escalated” is a real answer, not a blank.
neens.handoff.reasonstringWhy the agent handed off — "billing_dispute", "human_requested", whatever your taxonomy uses.
neens.feedback.ratingnumberAn in-conversation rating the end user gave (e.g. 1–5).
neens.feedback.commentstringA free-text comment the end user left.
neens.outcome.<kind>scalarAn open family — assert your own outcome kinds, e.g. neens.outcome.csat=4 or neens.outcome.resolved=true. See outcomes are not auto-measured.

The namespace is dotted, and the dots are meaningful. neens.handoff.reason lifts to nested metadata (handoff → reason), which is exactly the path a custom measure walks. Set the attribute with the literal dotted key — every OTel/OpenInference SDK treats attribute keys as flat strings, and Neens rebuilds the nesting.

Set them on a span

Emitted attributes are ordinary span attributes — no new endpoint, no special exporter. Set them however your tracing SDK sets any attribute, and send the trace the way you already send traces.

Any OpenTelemetry SDK. Set the attributes on the current span:

from opentelemetry import trace
 
span = trace.get_current_span()
span.set_attribute("neens.end_user_id", "user_42")
span.set_attribute("neens.escalated", True)
span.set_attribute("neens.handoff.reason", "billing_dispute")
span.set_attribute("neens.feedback.rating", 4)
span.set_attribute("neens.outcome.resolved", True)

Send the trace as usual. Authenticate with your agent API key (Authorization: Bearer nk_live_…) and post to the same ingest endpoint you already use — see Send traces. The attributes ride along on the span; there is nothing extra to call.

Provenance: who asserted it decides the tier

Every business number in Neens carries a provenance badge, and it is decided by who made the claim — never chosen. Emitted is one of four tiers:

ProvenanceWho asserted itExample
MeasuredYour end user or system of record — a helpdesk, CRM, or warehouse recorded it. The strongest claim.A CSAT survey your helpdesk stored, ingested as a business outcome.
EmittedYour agent, on the span. Useful, but the same agent whose work you’re checking.neens.escalated, neens.feedback.rating — this page.
InferredAn LLM read the transcript and decided. A directional signal.A judge or classifier labelling a case “resolved”.
DerivedNeens computed it from the traces with a deterministic rule. No assertion.cost_per_case, repeat_contact_rate.
⚠️

Emitted is your agent grading its own homework. When your agent writes neens.outcome.resolved=true, that is its opinion of its own work — genuinely useful, and badged Emitted precisely so nobody mistakes it for the customer’s own confirmation. If you want the customer’s answer, that arrives as a Measured business outcome from your system of record.

Emitted outcomes are not measured outcomes

The neens.outcome.<kind> family is deliberately not promoted into your business outcomes feed. An emitted neens.outcome.csat=5 stays an emitted signal on the session; it never appears as a measured business outcome on its own. That line is on purpose — silently reclassifying the agent’s own claim as a system-of-record fact would launder the weakest evidence into the strongest.

If you want an outcome to count as Measured, send it to the outcomes API from the system that actually knows it (your helpdesk, your billing system), the same way any measured outcome arrives — see Business outcomes. The two can happily coexist: the agent’s emitted guess and the system of record’s confirmed answer, side by side, which is exactly how you find the cases where the agent thought it succeeded and the customer disagreed.

Read an emitted attribute as a measure

Once your agent emits an attribute, you read it back as a custom measure whose source is Trace metadata — which is what gives it the Emitted badge. Because the lift is nested, the metadata_path is the dotted neens.* key with the prefix kept.

Create a metadata-source measure

On the Custom measures tab, add a measure whose source is Trace metadata and whose path is the attribute you emit — for an escalation rate, that’s neens.escalated:

FieldValue
SourceTrace metadata
Metadata pathneens.escalated
GrainPer case
AggregationShare / average

It reads as Emitted

The measure resolves over the sessions your agent tagged, and its provenance badge reads Emitted — sourced from the claim, never chosen. A neens.handoff.reason measure works the same way; a numeric one like neens.feedback.rating averages into an in-conversation CSAT.

Promote it to a KPI (optional)

A measure you can chart becomes a promise when you give it a target and a direction — see Promote a measure to a KPI. An escalation-rate KPI is usually lower is better.

Coverage stays honest. A window where no session carries the attribute reads —, not a misleading 0. Emitted signal is never required, so “the agent didn’t say” and “the answer is zero” stay separate — see how a number stays honest.

Submit end-user feedback directly

Not every team has a helpdesk to route a rating through. When the end user clicks a thumbs-up or answers a one-question CSAT prompt in your product, you can record it against the conversation with a single call — no integration required.

POST /sessions/{id}/feedback attaches feedback to a session using your agent API key. Send exactly one signal — the endpoint infers the kind from which one you send:

  • a numeric rating (1–5 CSAT), or
  • a thumb (up / down).

An optional comment (≤2000 characters) rides along with either.

export NEENS_BASE_URL="https://your-neens-host"
export NEENS_API_KEY="nk_live_..."   # your agent API key
 
curl -sf -X POST "$NEENS_BASE_URL/api/sessions/SESSION_ID/feedback" \
  -H "Authorization: Bearer $NEENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rating": 4,
    "comment": "Sorted my refund in one go."
  }'

Feedback submitted this way is recorded as a business outcome, so it reads with Measured provenance — it is the end user’s own answer, and deliberately a step above the agent’s emitted neens.feedback.* guess. The two coexist: emit neens.feedback.rating when your agent estimates satisfaction mid-trace, and POST here when the user actually tells you — then compare the cases where the agent thought it did well and the customer disagreed.

Repeat contact rate

The end_user_id your agent emits unlocks a question no single trace can answer: how often does the same person have to come back? Neens reads it as repeat_contact_rate — the share of end users who opened more than one case in the window — a derived measure computed straight from the emitted identity, with no extra configuration.

It only means something once your agent emits neens.end_user_id: without it, every case looks like a different person and the rate is undefined (it reads —, honestly, rather than a bogus 0). With it, you get a re-contact signal you can chart and promote to a KPI — usually lower is better.

Where end_user_id shows up

  • On the session — as a filterable field, so you can pull every case for one person.
  • In repeat_contact_rate — the derived measure above.
  • In privacy erasure — it’s the key an end user’s data is erased by.

Map onto standards

Emitted attributes are a Neens convention, but they sit comfortably next to the emerging standards where those cover the same ground:

  • gen_ai.evaluation.result — where the OpenTelemetry GenAI conventions carry an evaluation result on a span, Neens reads it as evaluation signal. Use neens.feedback.* for the end-user-supplied rating that those conventions don’t yet standardise.
  • OpenInference session annotations — if you already annotate sessions via OpenInference, keep doing so; add the neens.* keys for the business facts (escalation, handoff, end-user identity) that have no annotation of their own.

The neens.* namespace fills the gaps rather than competing: business facts about a conversation that no cross-vendor convention has landed on yet.

  • Send traces — how to get spans (and these attributes) into Neens.
  • Custom measures — turn an emitted attribute into a measure you can chart.
  • Business outcomes — the Measured tier, for facts your system of record confirms.
  • Derived measures — repeat_contact_rate and the rest of the zero-setup tier.
  • Business KPIs — promote any of these to a tracked commitment.

End-user identity and privacy

neens.end_user_id is personal data — it names a real person. Neens stores it as a queryable field on the session so you can both analyse re-contact and honour a deletion request: an end user’s data is erased by that id. Emit a stable, non-guessable id (your own user key, not an email address in the clear) so the same person links across cases without exposing their contact details in your traces.