Agenta
Agenta (MIT outside ee/) is an open-source LLMOps platform with native OTLP trace ingest. Its server-side adapters read the OpenInference, OpenLLMetry and OTel GenAI conventions, so the plugin's spans arrive with span types, token roll-ups and sessions already filled in, and its POST /api/spans/query API lets the plugin dashboard read them back.
Signals: traces only. Deployment: local (docker compose, eleven containers in the trimmed stack) · Agenta Cloud (US / EU). Cost: open source · free cloud tier.
One real hermes turn through type: agenta into the bundled compose stack (api image v0.122.x, 2026-10-11) stored 7 spans with Agenta's ag.metrics.tokens roll-ups, and the dashboard-verify harness read them back through the adapter. See Verified behavior.
Quick start
docker compose -f docker-compose/agenta/docker-compose.yaml up -d # ~1-2 min
export OTEL_AGENTA_API_KEY="$(docker-compose/agenta/mint-api-key.sh)"
The script creates a local account (sign-ups are open on a fresh stack), signs in through SuperTokens and mints a project API key with POST /api/keys. Then declare the backend:
backends:
- type: agenta
endpoint: http://localhost:3060 # the type appends /api/otlp/v1/traces
api_key_env: OTEL_AGENTA_API_KEY
The startup banner reads ✓ Agenta at http://localhost:3060/api/otlp/v1/traces (traces only). Open http://localhost:3060 (log in as hermes@example.com / HermesOtel-local-1 by default) and the turn is under Observability in the default project.
For Agenta Cloud, leave endpoint out and set region: us (default) or eu; the key comes from the project's settings.
Why a dedicated type: agenta
Export also works through the generic OTLP type with a hand-written Authorization header and metrics: false, logs: false. Declaring type: agenta instead asks the plugin to:
- Complete
endpointto<origin>/api/otlp/v1/traces(an origin,/api,/api/otlpor the full URL all work), or use the cloud host forregionwhen there is none. - Read the project key from
api_key/api_key_env(falling back toOTEL_AGENTA_API_KEY, then the SDK'sAGENTA_API_KEY) and send it asAuthorization: ApiKey <key>; skip the entry at startup when none resolves. - Keep metrics and logs off: Agenta mounts only the traces route.
- Enable the dashboard adapter for the entry.
Configuration reference
| Field | Meaning |
|---|---|
endpoint | Agenta's origin for a self-hosted stack, e.g. http://localhost:3060. Also via OTEL_AGENTA_ENDPOINT or the SDK's AGENTA_HOST. Unset means Agenta Cloud. |
region | us (default) or eu, for the cloud host when endpoint is unset. |
api_key / api_key_env | Required. A project API key → Authorization: ApiKey <key> (fallbacks OTEL_AGENTA_API_KEY, AGENTA_API_KEY). Prefer api_key_env. |
metrics / logs / traces | Per-signal toggles; metrics and logs default off (no such routes). |
headers | Extra headers, merged on top of the generated ones. |
ui_url | Dashboard read path: the project's observability page, http://<host>/w/<workspace>/p/<project>/observability, for an "open in" link. |
service_name | Dashboard read path: the service shown on cards and in the detail's resource, because Agenta does not store resource attributes. Unset means the plugin's own service.name. |
No metric-temporality preset (no metrics). The agent root keeps its token roll-up: Agenta's cumulative total on the root equalled the sum of the api.* spans in the verified turn, so nothing is double-counted.
Single backend (env vars)
export OTEL_AGENTA_API_KEY=... # alone: Agenta Cloud (US)
export OTEL_AGENTA_ENDPOINT=http://localhost:3060 # self-hosted
export OTEL_PROJECT_NAME=hermes-otel-agenta
AGENTA_API_KEY alone never switches export on (it is the SDK's variable and often set for other tools); one OTEL_AGENTA_* variable is the opt-in.
What you'll see
Agenta types every span (agent for the root, chat for llm.* and api.*, tool for tool.*), groups the turn under the session the plugin sets (session.id → ag.session.id), and computes ag.metrics.tokens (cumulative down the tree, incremental per span), ag.metrics.duration and, for priced models, ag.metrics.costs. The original attributes stay next to Agenta's own under their namespaces (gen_ai, llm, hermes, openinference, input, output).
Dashboard adapter
The Hermes dashboard's OTel tab reads Agenta through POST /api/spans/query with the same project key:
- Search: a
TracingQuerywithwindowing(oldest/newestfrom the window and the cursor,order: descending) andfilteringconditions:parent_id is nullfor roots-only,span_name is/startswithfor the name and kind filters,attributeswith a dottedkey(hermes.session_id,llm.model_nameorgen_ai.request.model, andk=vpairs from the raw box) witheq, the tool filter asspan_name is tool.<name>,status_code is/is_not STATUS_CODE_ERROR, and free text ascontainsoninput.value,output.value,ag.data.outputsandag.unsupported.data.inputs; each "or" is one query per key merged client-side, because the API accepts a nestedorgroup but drops it (v0.122: a text search returned every root). The grammar has no duration field, so the minimum duration is applied to the fetched rows. Span counts come from one more query over the page's trace ids. - Detail: the trace's spans, flattened from Agenta's nested attribute objects to dotted keys, with Agenta's derived
ag.data.*,ag.meta.*andag.unsupported.*copies of the inputs and outputs dropped (the plugin's owninput.valueandgen_ai.*.messagesalready carry them) andag.metrics.*/ag.type.*kept. Resource attributes are not stored, so the resource is the entry'sservice_name. - Current Agenta (
agenta-api:latest, 2026-10-18) no longer storesllm.model_name,tool.nameor a root'sinput.value/output.valueas sent: it keepsgen_ai.request.model,gen_ai.tool.nameand the span name, and moves the text toag.data.outputs/ag.unsupported.data.inputs. The search asks both the old and the new keys, and a card or detail span missing one of those four keys shows the moved value under it. serviceis not a filter (the key is the project), and no "open in" link is offered unlessui_urlnames the observability page.
Verified behavior
| Check | Result |
|---|---|
| Ingest | POST /api/otlp/v1/traces with ApiKey → 200 (GET → {"status":"ready"}); without a key → 401; a JSON body → 400 |
| Spans | 7 in one trace: agent (type agent, no parent), llm.nvidia/… and three api.nvidia/… (type chat), tool.terminal, tool.read_file (type tool), all STATUS_CODE_OK |
| Roll-ups | root ag.metrics.tokens.cumulative.total = sum of the three api.* spans (23 653 in the verified turn): no double count |
| Query grammar | parent_id is null → the root; span_name startswith "tool." → 2; attributes key hermes.session_id eq → 1 root; status_code is_not STATUS_CODE_ERROR → 7; attributes key input.value contains → 6; oldest / newest honoured; not_exists and eq on parent_id / status_code are rejected ("only supports comparison and list operators") |
| Dashboard verify harness | fresh four-turn batch: PASS 35 · SKIP 13 (the skips: no UI deep link ×4 and the nine metrics / logs checks Agenta does not serve); root, status, model, session, tokens, start and duration of every turn match the oracle, the free-text marker search returns exactly one trace each, the kind=tool filter names the three tool-using turns, and the cards and detail render in the browser |
| Resident memory | about 2 GB across eleven containers (api ~900 MB) |
Caveats
- Protobuf only on the OTLP route (the plugin's default); batches over 10 MB are 413.
- Asynchronous ingest: spans pass through a Redis stream and appear a few seconds after the export; a 200 means accepted, not stored.
- Heavy: the trimmed stack is eleven containers and about 4 GB of images; the upstream stack adds a sandbox runner (needs
SYS_ADMIN+/dev/fuse), playground services, a mobile UI and Traefik. - Resource attributes are dropped on ingest; the dashboard shows the entry's
service_nameinstead. - Sign-ups are open on a fresh stack; the compose header lists the local secrets to change before exposing the port.
Troubleshooting
agenta requires api_key— no key resolved; runmint-api-key.shor copy one from the project settings.- 401 on every export — the key belongs to another stack or was deleted (
GET /api/keyslists the live prefixes). - Spans missing right after a turn — the ingest is asynchronous; wait a few seconds.
- Dashboard says
Agenta needs api_key— the adapter reads the sameapi_key_envas the exporter; set it on the entry.