Services, External Services, and Projects
Every Entity Hexcovery observes falls into one of three classes, and the classification rests on a single OpenTelemetry attribute: service.namespace.
Classification is by namespace, NOT by traces
A common misconception is that "things with traces are services." That's wrong. Traces are just one signal. The class of an Entity is decided by whether its telemetry carries a service.namespace — nothing else.
The three classes
| Class | Rule | Examples |
|---|---|---|
| Service | Telemetry carries a service.namespace → it is attributed to a Project |
your Rails app, an auth microservice, even caddy (logs only) |
| External Service | Seen only as a call-target, with no service.namespace → a dependency that belongs to no Project |
postgres, redis, smtp (until they gain an identity) |
| Infrastructure | The hosts / Kubernetes nodes your software runs on — a separate substrate | a VM, a k8s node |
Two consequences worth internalizing:
caddyis a Service because it carries aservice.namespace— even though it's third-party software you didn't write.postgresis an External Service because it has no namespace — even though it's "software" too. They do not sit together: the criterion is the namespace, full stop.- Traces are a facet, not the boundary. Having traces marks a Service as "instrumented by us / our own code" (your app is instrumented;
caddyis external software you only collect logs from). That's a badge within the Services group — not the Service / External Service line.
How you make something a Service
The act of making a piece a Service of your Project is giving its telemetry a service.namespace. That attribute is the attribution mechanism.
A Project collects; it does not own
A Project is a service.namespace. It is an aggregation lens, not a container with ownership. It reunites and connects the entities that share its namespace to deliver:
- at-a-glance health for everything in it, and
- the "this error ← that piece" correlation.
The entities exist on their own first; the Project assembles them. The global, standalone screens (Services, Traces, Logs, metrics) remain the deep-dive tools — the Project is the hub that assembles, the tools explore.
Naming in the UI
A Project is literally a service.namespace. The left-nav may label it "Namespace Projects" to bridge the OTLP field you configure to the product word; it's just "Project" everywhere else. The pieces inside it are Services, which map exactly to service.name.
The namespace is the boundary (many-to-many, asymmetric)
The grouping relationships are intentionally lopsided:
- A Service belongs to exactly one Project — its
service.namespaceis its home. It is never a native member of another Project. -
A Project can hold several Services, all sharing that namespace.
Example: Project SUPERPIPPO = { PIPPO (the main app) + PLUTO (the auth microservice) }.
-
An External Service has no home. With no namespace, it appears as a dependency inside every Project that calls it, shared among them.
-
A Service can still be referenced as a dependency by other Projects (a cross-Project call). There it shows as an edge that links back to its home Project.
Example: PIPPO (home: SUPERPIPPO) is called over HTTP by a service in Project PAPERINIK → inside PAPERINIK it appears as a dependency edge that links to PIPPO's home.
\"Dependency\" is a relationship, not a type
A dependency is an edge between entities — not a fourth class of thing. The same Entity can be a first-class Service in its home Project and a dependency edge as seen from another.
Reconciliation: identity vs section
A call-target is observed as an edge keyed by server.address — the caller's spans don't carry the target's own service.namespace. Reconciliation maps that endpoint to a known Entity:
- Reconciliation decides identity — "which Entity is this endpoint?"
service.namespacedecides the section — Service vs External Service.
So an edge that resolves to a namespaced Entity is shown as a Service (possibly cross-Project); an edge that resolves to nothing — or to something still without a namespace — stays an External Service. Reconciliation is automatic where possible (matching server.address against known identities), with a manual link as the fallback.
Graduation: an External Service becoming first-class
An External Service is edge-only — visible only as a dependency inside the Projects that call it — until it acquires its own identity. That typically happens when you attach its OTLP metrics (giving it a stable identity), or when you reconcile it to a namespaced Entity.
At that point it graduates into a Service: it gains its own home, a presence in the catalog, and a metrics lens of its own.
External Service Service
(dependency edge only) ── graduates ─► (own home + metrics lens)
e.g. postgres, redis via OTLP e.g. a postgres with its
seen via callers' metrics own postgresql receiver
traces
This is exactly what attaching infrastructure metrics (the OTel receivers for PostgreSQL, Redis, Kafka, …) buys you: external software stops being an anonymous edge and becomes a first-class Entity you can observe on its own.
Global truth vs per-Project views
Every Entity has both:
- a global truth — on its dedicated screen: all callers, all signals, overall health;
- per-Project views — inside a Project, it's shown scoped to that Project's relationship with it (e.g. a shared database shown with the errors as seen from this Project's calls).
How the model is surfaced
- By signal (one lens, all entities, filterable by Project): Traces, Logs, Cron, Metrics — plus Hosts / Kubernetes for the infra substrate. The deep-dive tools.
- By Project (one Project, all its pieces + health): the Project hub, with sections Services · External Services · Infrastructure.
- By entity (one entity, all its signals): the per-entity detail view — see Entities and signals.
- The global Services catalog is presence-based across everything with a
service.namespace. It's the discoverability front-door for people who don't yet think in Projects. External Services (no namespace) are not in it — they live inside Projects as edges until they graduate.
Where to go next
- Entities and signals — what an Entity is and how its signals evolve.
- Projects in the dashboard and the Services catalog.
- Send infrastructure metrics to graduate your dependencies.
- Query it all with OQL.