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
| What | A neens.* span-attribute namespace your agent sets to state business facts about a turn. |
| How | Set the attributes on any span with your tracing SDK; Neens lifts them at ingest. No extra endpoint to call. |
| Where it lands | On the session — an end_user_id you can filter and erase by, plus nested metadata a custom measure can read. |
| Provenance | Emitted — your agent’s own claim. Distinct from Measured (your system of record) and Inferred (an LLM). |
| Setup | None 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.
| Attribute | Type | Meaning |
|---|---|---|
neens.end_user_id | string | Your 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.escalated | boolean | Whether 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.reason | string | Why the agent handed off — "billing_dispute", "human_requested", whatever your taxonomy uses. |
neens.feedback.rating | number | An in-conversation rating the end user gave (e.g. 1–5). |
neens.feedback.comment | string | A free-text comment the end user left. |
neens.outcome.<kind> | scalar | An 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:
| Provenance | Who asserted it | Example |
|---|---|---|
| Measured | Your 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. |
| Emitted | Your agent, on the span. Useful, but the same agent whose work you’re checking. | neens.escalated, neens.feedback.rating — this page. |
| Inferred | An LLM read the transcript and decided. A directional signal. | A judge or classifier labelling a case “resolved”. |
| Derived | Neens 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:
| Field | Value |
|---|---|
| Source | Trace metadata |
| Metadata path | neens.escalated |
| Grain | Per case |
| Aggregation | Share / 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. Useneens.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.
Related
- 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_rateand 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.