business-attr-ux
Skill1 starsChanged 48 days agoApache-2.0View on GitHub
Tiered confidence-table UX, confidence thresholds, and conflict-resolution protocol for confirming inferred service identity and business attributes. Use from /otel-business-attrs.
In this repository18 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- otel-backendSkill
- otel-business-attrsSkill
- otel-collectorSkill
- otel-evaluateSkill
- otel-initSkill
- otel-instrumentSkill
- otel-remediateSkill
- otel-uninstrumentSkill
- semconv-disciplineSkill
- new-backendSkill
- validate-pluginSkill
- collector-topologySkill
- deployment-remediationSkill
- language-maturitySkill
- semconv-disciplineSkill
- terraform-patternsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. About 0 are common, 5 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Write immediately. Show a collapsed "✓ Auto-applied" block so the user can see what was written without being asked to confirm it.
- Very specific: line 58
If namespaceHint is known (or the user has supplied a namespace), present candidates already namespaced — …
- Very specific: line 61
If no namespace is known yet, ask for it *before* presenting business candidates.
- Very specific: line 63
If a candidate is somehow shown as biz.*, say in the same breath that biz.
- Very specific: line 67
Never write a bare biz.* name to the context cache: it is non-conformant, and the user approved a different string than the one that …
- Very specific: line 139
unit(optional, best-effort): When the source makes an instrument's unit directly observable (e.g. - Distinctive: line 145
After the user responds:
- Very specific: line 152
Do NOT proceed to write until all Tier 2 items have been explicitly acted on, and every approved business attribute has a confirmed kind.
- Distinctive: line 157
When multiple sources disagree on the same attribute value, show a conflict block:
- Distinctive: line 170
NEVER silently pick one source.
- Very specific: line 171
Write the chosen value with "conflict_resolved": true in the context JSON.
- Distinctive: line 173
Add a comment in the written manifest pointing this out.
- Very specific: line 233
All of these are user-owned fields in the cache ownership contract: a later re-scan carries them over verbatim and must never reset them to …
- Very specific: line 238
Every object in businessAttrs MUST include "confirmed": true — the write-guard hook rejects the whole file otherwise.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Business Attribute Inference UX
Confidence Tiers
Tier 1 — Auto-write (confidence ≥ 0.9)
Write immediately. Show a collapsed "✓ Auto-applied" block so the user can see what was written without being asked to confirm it.
Example sources that reach Tier 1:
service.versionfrom the language's manifest (package.json#version,pyproject.toml [project].version,*.csproj's<Version>,*.gemspec'sspec.version,pom.xml's<version>) → confidence 0.97. No manifest source forgo— Go module versions come from VCS tags, notgo.mod— so omit it there rather than guessing.
service.name is NOT derived here at all — see #57/#132. The scanner already resolved it
under its own observed-name ladder (agents/repo-context-scanner.md) and recorded it as
services[i].name with nameSource/nameConfidence, plus a conflicts[] entry if a source
disagreed. Show it in the Auto-applied block as a carried-over fact, not a freshly computed Tier
1 candidate — computing a second answer from the manifest here is exactly how #132 overwrote an
already-correct observed name (bootstrap-literal / env:OTEL_SERVICE_NAME) with a stale
manifest name at auto-write confidence. A service.name conflict is resolved through the
conflict-resolution protocol below, never by re-deriving it in this tier.
Tier 2 — Confirm (0.5–0.9)
Show in an approval table. Each row shows: attribute | proposed value | confidence | source. Each row has three actions: [A]pprove / [E]dit / [R]emove. User must explicitly approve before any Tier 2 value is written.
Example sources that reach Tier 2:
service.namespacefrom directory structure → confidence 0.71service.teamfrom CODEOWNERS → confidence 0.63 (CODEOWNERS may be stale)deployment.environment.namefrom CI config → confidence 0.55
Tier 3 — Propose (confidence < 0.5)
Show as flagged proposals. NEVER write without explicit user action.
User must promote to Tier 2 by typing the row number (this acts as immediate approval — no second confirmation needed) or reject by typing 'r <number>'.
Example sources that reach Tier 3:
- Business transaction name inferred from route analysis → confidence 0.35–0.45
- Domain inferred from git remote URL → confidence 0.40
Business Attributes — Always Confirm
Any candidate business metric or business attribute is ALWAYS presented as Tier 2 (approval required), regardless of confidence score. Business semantics are NEVER assumed. This rule overrides the Tier 1 threshold.
biz. is a placeholder, never a name that gets written. Inference produces a bare
shape like biz.checkout.conversion_rate, but semconv-discipline requires every custom
attribute to carry a reverse-DNS prefix and names bare prefixes as WRONG. Both rules cannot be
followed at once, so resolve it before the user sees the row rather than after they approve it:
- If
namespaceHintis known (or the user has supplied a namespace), present candidates already namespaced —com.myorg.checkout.conversion_rate. What the user approves is then exactly what gets written. - If no namespace is known yet, ask for it before presenting business candidates. That
question is Step 3 of
/otel-business-attrs; run it first when there are candidates to show. - If a candidate is somehow shown as
biz.*, say in the same breath thatbiz.is a placeholder that will be replaced at write time, and record the pre-namespace form ascandidateNamealongside the finalname.
Never write a bare biz.* name to the context cache: it is non-conformant, and the user
approved a different string than the one that reaches disk.
Enforced, not just requested. Each businessAttrs entry written to
.claude/otel-context.json MUST carry "confirmed": true, set only after the user
explicitly approves it. The write-guard PreToolUse hook parses every write to
otel-context.json and BLOCKS it if any business attribute is missing "confirmed": true.
So an unconfirmed business attribute cannot reach disk even if this prose is ignored — write
approved attributes with "confirmed": true, and never write rejected/unreviewed ones.
Confidence Thresholds
AUTO_WRITE_THRESHOLD = 0.9
CONFIRM_THRESHOLD = 0.5These are calibrated constants. Do not change them without user instruction.
Presenting the Confirmation Table
Format:
✓ Auto-applied (confidence ≥ 0.90):
service.name = "checkout-api" [0.97 · already resolved by the scanner: bootstrap-literal]
service.version = "1.4.2" [0.97 · package.json#version]
⚠ Needs your approval (confidence 0.50–0.89):
# Attribute Proposed Value Conf Source
1 service.namespace "payments" 0.71 dir: services/payments/
2 service.team "@payments-team" 0.63 CODEOWNERS line 4
Actions: [A]pprove all [number] approve one [E number] edit [R number] remove
→
● Business attributes (always confirm) — namespace com.myorg:
# Attribute Kind? Source
1 com.myorg.checkout.orders_placed counter route POST /checkout (AST inference)
2 com.myorg.checkout.conversion_rate gauge route POST /checkout (AST inference)
3 com.myorg.checkout.customer_tier dimension route POST /checkout (AST inference)
4 com.myorg.checkout.payment_duration histogram route POST /checkout (AST inference)
Actions: [A]pprove [K number counter|gauge|dimension|histogram] set kind [R number] reject
→Each business attribute carries a kind, confirmed like everything else here — never guessed
silently downstream. It decides how /otel-backend renders the attribute on a dashboard, and the
counter/gauge split matters because the aggregation differs (a rate is only valid on a counter):
counter— a monotonically increasing total you view as a rate:orders_placed,checkouts_completed, anything_total/_count. Rendered asrate(...)over time.gauge— a level / ratio / value read as-is, NOT rated:conversion_rate,cart_value,queue_depth, anything_rate/_ratio/_value. Rendered as the value directly (avg / latest).dimension— a low-cardinality attribute you break traffic down BY:customer_tier,plan,region. Rendered as a breakdown (facet / group-by) of request volume.histogram— a duration or size distribution:payment_duration,upload_size,queue_wait_time, anything_duration/_latency/_size/_bytes. Rendered as quantiles (p50/p95/p99), never as an average — an average is the one view that hides exactly what a histogram exists to show (a p99 that doubles while the mean holds flat is invisible on an average panel, which is the ordinary shape of a latency regression). Filing a duration asgaugeplots its average instead and loses this; filing it ascounterand rating it is meaningless. This is the same reasoning as the counter-vs-gauge split, one step further out.
Propose a kind from the candidate's shape (_total/_count → counter; _rate/_ratio/
_value → gauge; _duration/_latency/_size/_bytes, or an observed instrument unit of
s/ms/By → histogram; a categorical noun → dimension), but it is a proposal the user can
flip with [K <n> counter|gauge|dimension|histogram]. A high-cardinality identifier (an id,
email, raw UUID) is none of these — it belongs in derived.highCardinalityAttributes, not here;
do not accept it as a business dimension.
unit (optional, best-effort). When the source makes an instrument's unit directly
observable (e.g. meter.CreateHistogram<double>("...", unit: "s", ...)), carry it through to the
written entry — a quantile panel needs it to label its axis (ms, s, bytes), and it is
otherwise only recoverable by re-reading the instrument definition in source. null when not
observed; never guess one from the name.
After the user responds:
- "a" or "A" → approve all items in that tier (with their currently-shown
kind) - A number (e.g. "1") → approve that specific item
- "k 2 gauge" → set item 2's kind before approving (also accepts
histogram, e.g. "k 4 histogram") - "e 2" → edit item 2's value (ask for new value inline)
- "r 1" → remove item 1 from the proposal
Do NOT proceed to write until all Tier 2 items have been explicitly acted on, and every approved
business attribute has a confirmed kind.
Conflict Resolution Protocol
When multiple sources disagree on the same attribute value, show a conflict block:
⚡ Conflict: service.name
1. package.json#name → "checkout-api" [confidence 0.97]
2. repo path → "services/cart" [confidence 0.70]
3. Dockerfile LABEL → "cart-service" [confidence 0.88]
Pick a number, or type a custom value:
→Rules:
- NEVER silently pick one source. Always surface the conflict.
- Write the chosen value with
"conflict_resolved": truein the context JSON. - The conflict itself is diagnostic — it indicates the team has not aligned on a service name. Add a comment in the written manifest pointing this out.
Written Output Format
agents/repo-context-scanner.md owns the context-JSON schema — do not restate it here, or the
two copies drift and later commands read whichever they happen to find. This section covers
only the fields this skill is responsible for producing.
On approval, update .claude/otel-context.json in place, changing only:
{
"namespace": "com.myorg",
"deploymentEnvironment": "production",
"confirmedAt": "<ISO-8601 timestamp>",
"businessAttrs": [
{
"name": "com.myorg.checkout.conversion_rate",
"candidateName": "biz.checkout.conversion_rate",
"kind": "gauge",
"unit": null,
"source": "route POST /checkout (AST inference)",
"confidence": 0.42,
"confirmed": true,
"confirmedAt": "<ISO-8601 timestamp>"
},
{
"name": "com.myorg.checkout.payment_duration",
"candidateName": "biz.checkout.payment_duration",
"kind": "histogram",
"unit": "s",
"source": "route POST /checkout (AST inference)",
"confidence": 0.42,
"confirmed": true,
"confirmedAt": "<ISO-8601 timestamp>"
}
]
}unit is always present (nullable) once this fix lands — null for every kind where it doesn't
apply or wasn't observed, not just omitted. A cache written before unit existed simply lacks the
key; treat an absent unit the same as null, never as a reason to skip the entry.
and, per service, the identity fields the user confirmed:
{
"namespace": "payments",
"namespaceSource": "user-confirmed",
"namespaceConfidence": 0.71,
"team": "@payments-team",
"teamSource": "user-confirmed",
"teamConfidence": 0.63
}plus resolvedValue / resolvedSource / resolvedAt / "conflict_resolved": true on each
entry in conflicts the user settled.
All of these are user-owned fields in the cache ownership contract: a later re-scan carries
them over verbatim and must never reset them to null or []. Mark a confirmed namespace or
team "user-confirmed" as its source — that marker is what tells the scanner not to overwrite
it with a fresh inference.
Every object in businessAttrs MUST include "confirmed": true — the write-guard hook
rejects the whole file otherwise. Omit attributes the user rejected or did not review; never
write them with "confirmed": false expecting them to be ignored. Never write a bare biz.*
name; keep the pre-namespace form in candidateName.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

