Reference
Read as MarkdownAgent span and event attributes
The identifier rules and Anectico-defined attributes for agent spans, context events, guardrail decisions, and A2A calls.
Most of what Anectico reads off an agent span comes from a published convention — OpenTelemetry’s GenAI semantic conventions, or OpenInference. This page documents the identifier rules applied to that metadata and the attributes no convention defines: context assembly, the guardrail decision, and the A2A call.
What an absent value means
An attribute you do not send is recorded as absent, and absent never becomes a value. This is the single most important rule on the page, and it runs in both directions:
- If you send no
anectico.policy.decision, the run reports no decision — notallow. A guardrail that ran and reported nothing about a policy is a different fact from a guardrail that permitted the work, and Anectico keeps them apart. - Conversely, do not send
allowto mean “nothing happened.” Send it only when a policy considered the work and permitted it.
The same rule applies to every optional field below.
Bounded values
Each attribute has a value shape, and a value outside it is refused, not truncated — the field is left empty and the rest of the record is kept. Four shapes are used:
| Shape | Meaning |
|---|---|
| Identifier | [A-Za-z0-9._:/-], 1–128 bytes. The base shape for fields that NAME something. |
| Model identifier | [A-Za-z0-9._:/@-], 1–128 bytes. @ admits vendor model revisions such as gpt-4o@2024-08-06. |
| Version identifier | [A-Za-z0-9._:/+-], 1–64 bytes. - admits prereleases and + admits build metadata, such as 1.4.2-rc.1+build.7. This is a bounded identifier shape, not strict Semantic Versioning validation. |
| Code | [A-Za-z0-9._:/-], 1–64 bytes. For fields that CLASSIFY something. |
These fields are grouped and filtered on, so they must stay short and closed. Free text — a sentence, a matched string, a model’s words — belongs in the opt-in content capture path, never here.
Canonical span identifiers
The same bounds apply to identifiers read from OpenTelemetry GenAI and OpenInference attributes.
Provider names, agent IDs and names, workflow names, conversation and turn IDs, tool names and call
IDs, response IDs, MCP session IDs, and effective-configuration hashes use the Identifier shape.
gen_ai.request.model, gen_ai.response.model, and OpenInference llm.model_name use the Model
identifier shape. gen_ai.agent.version and mcp.protocol.version use the Version identifier
shape. Finish reasons use Code. gen_ai.operation.name is an Identifier and is limited to 128
bytes.
These field-specific additions follow the published value contracts instead of widening every
identifier. The OpenTelemetry GenAI registry
defines model and agent-version values as strings, while its normative model
source
requires the exact vendor model name. OpenInference
likewise records llm.model_name as the actual model identifier. Semantic Versioning
uses - before prerelease identifiers and + before build metadata. The MCP tool-name
grammar is the narrower
[A-Za-z0-9_.-] subset, so it fits the base Identifier shape. The MCP telemetry
convention
currently defines no server-name attribute; registry-style reverse-DNS names such as
com.example/invoice-server also fit the base shape under the MCP registry server-name
schema
if such a canonical field is added later.
An invalid value is omitted from canonical metadata and does not participate in derived agent or memory identities. The original source attribute can be retained only when your content-capture policy permits it; putting prose or personal data in an identifier never bypasses that policy.
For memory item telemetry, an invalid non-empty gen_ai.data_source.id also rejects the derived
item identity. It is not treated as though the store were absent, so two invalid store claims
cannot collapse into one item.
Provider attribution distinguishes an inference host from an agent framework. The literals
autogen and strands-agents identify AutoGen and Strands respectively and are not recorded as
inference providers. When telemetry also declares an actual provider through a supported provider
or system attribute, that value remains the provider.
Context events
Context evidence is carried as one of three span events on the model-call span. It contains only identifiers, counts, codes, ordered lists, and SHA-256 digests. Prompt text, document bytes, memory content, summaries, and tool-result content are never accepted. The SDKs refuse the entire call if any value is invalid and refuse to attach these events to a run or tool span.
| Event | Attribute | Bound or meaning |
|---|---|---|
anectico.context.assembly |
anectico.context.budget_tokens |
Integer from 0 through 4,294,967,295. |
anectico.context.reserved_output_tokens |
Integer from 0 through 4,294,967,295. | |
anectico.context.assembled_tokens |
Integer from 0 through 4,294,967,295. | |
anectico.context.overflow |
none, truncated, or dropped. |
|
anectico.context.assembly_sha256 |
Required lower-case SHA-256 over the exact source preimage below. | |
anectico.context.sources |
JSON string with at most 64 entries and at most 8 KiB. | |
anectico.context.compaction |
anectico.context.compaction.trigger |
token_threshold, turn_threshold, manual, or cache_expiry. |
.strategy |
summarize, drop_oldest, drop_tool_results, or dedup. |
|
.threshold_tokens, .retained_overlap_tokens, .before_tokens, .after_tokens |
Integers from 0 through 4,294,967,295; after_tokens cannot exceed before_tokens. |
|
.summarizer_model, .summarizer_version |
Optional identifiers in [A-Za-z0-9._:/-], at most 128 / 64 bytes. |
|
.before_sha256, .after_sha256 |
64 hexadecimal characters. | |
.removed_source_ids |
JSON array of at most 64 identifiers. | |
anectico.context.cache |
anectico.context.cache.provider |
anthropic, openai, gemini, adk, or custom. |
.key_sha256 |
Optional; 64 hexadecimal characters when present. Provider wrappers omit it because provider usage responses do not expose a stable cache-key preimage. | |
.config_version |
Optional identifier in [A-Za-z0-9._:/-], at most 64 bytes. |
|
.outcome |
hit, miss, write, refresh, or expire. |
|
.ttl_seconds, .reused_tokens, .latency_saved_ms |
Integers from 0 through 4,294,967,295 when present. A reported zero is distinct from absence. | |
.cost_saved_nanos |
Non-negative integer when present. A reported zero is distinct from absence. |
Each object inside anectico.context.sources has these bounds:
| Field | Bound |
|---|---|
kind |
system, developer, user, history, retrieved, memory, tool_result, file, skill, or summary. |
id |
Identifier in [A-Za-z0-9._:/-], 1–128 bytes. A memory source uses its lower-case 64-hex memory_item_key. |
version, owner |
Optional identifiers in [A-Za-z0-9._:/-], at most 64 bytes each. |
sha256 |
Optional 64-character hexadecimal digest. |
sensitivity |
public, internal, confidential, restricted, or unspecified. |
tokens, position |
tokens is 0 through 4,294,967,295; position is 0 through 65,535. |
visibility |
included, excluded, or truncated. |
reason |
Optional code in [A-Za-z0-9._:/-], at most 64 bytes, such as budget, policy, dedup, or stale. |
The assembly digest binds the event to its source coordinates. Sort the sources by position
ascending, then encode one UTF-8 line per source as:
position<TAB>id<TAB>visibility<LF>
position is decimal, id is verbatim, and the final source line also ends in LF; there is no
other trailing content. SHA-256 that complete byte string and emit the 64-character lower-case hex
digest. The Go, JavaScript, and Python SDKs compute this value from sources; an optional
caller-supplied assertion is refused if it differs. The attribute remains required on raw
telemetry: Anectico does not fill a missing digest, and a missing or mismatched digest rejects the
whole assembly event.
There is deliberately no producer-supplied compaction ratio. Anectico derives it as
after_tokens / before_tokens; sending a ratio through raw telemetry can cause the whole event to
be rejected if it disagrees. See Record context assembly for SDK examples.
Guardrail decisions
Send these on the span for the work the guardrail evaluated — normally your agent-run span. The
SDKs do it for you: run.RecordGuardrail(...) (Go), run.recordGuardrail({...}) (JavaScript),
run.record_guardrail(...) (Python).
| Attribute | Shape | Required | Meaning |
|---|---|---|---|
anectico.guardrail.name |
Identifier | Yes | The guardrail that ran. This is what makes the evaluation visible at all — without it the other five are ignored. |
anectico.guardrail.result |
Code | No | What the guardrail FOUND, in your own vocabulary: passed, triggered, flagged. |
anectico.guardrail.finding_class |
Code | No | The KIND of finding: pii, prompt_injection, toxicity, secret. Never the finding itself. |
anectico.policy.id |
Identifier | No | The policy in force. |
anectico.policy.version |
Code | No | Its version, so a change in behaviour can be attributed to a change in policy. |
anectico.policy.decision |
Closed set | No | What the policy DID: allow, deny, modify, escalate. |
result and decision are separate on purpose. A guardrail that triggers under a policy that only
logs is a real and common pair, and one field cannot hold it.
anectico.policy.decision accepts exactly four values:
| Value | Meaning |
|---|---|
allow |
The work was permitted. |
deny |
The work was stopped. |
modify |
The work continued in altered form — a redaction, a mask, a rewrite. |
escalate |
The work was handed to a person. |
Anything else is refused and the field is left empty; it is never mapped to the nearest value.
Recording a guardrail does not change what the span IS
These attributes are an annotation. A span that declares a guardrail name keeps the kind of work
its own gen_ai.operation.name says it is — record a guardrail on your agent-run span and the step
is still an agent invocation, not a safety step. Only a span that names no operation at all takes
its classification from the guardrail, which is what a framework’s own guardrail span does.
The same rule holds for a handoff: it annotates the work it was declared on and never renames it.
The run’s counts still report the guardrail, twice and deliberately:
- Guardrail checks counts every step on which a guardrail ran — one your instrumentation named, or one a framework reported as safety work without saying anything more. Each step is counted once, however it qualified.
- Guardrail decisions counts only the steps that declared a guardrail name.
The gap between them is how much of your guardrail surface is instrumented.
A2A calls
a2a.* attributes describe one Agent-to-Agent protocol call. A span carrying a2a.method.name is
classified as A2A work.
| Attribute | Shape | Required | Meaning |
|---|---|---|---|
a2a.method.name |
Code | Yes | The protocol method — SendMessage, GetTask. This is what makes the call visible as A2A. |
a2a.task.id |
Identifier | No | The task the call concerned. A first message that opens a task carries none; the response names it. |
a2a.context.id |
Identifier | No | The context grouping related tasks. |
Anectico’s own transport span also carries a2a.role and a2a.protocol.version as diagnostic
attributes. They are not fields in the canonical A2A step, so do not rely on custom values there
being validated or surfaced with the method, task, and context identifiers.
What this covers today. Anectico’s own A2A surface emits these attributes for every non-streamed call it serves, so A2A work you drive against Anectico appears in your execution graph. Streamed A2A calls are not yet observed. Your own agent-to-agent traffic appears only if your agents emit these attributes themselves.