Skip to content

feat(otel): set ate.* actor telemetry identity on ateapi spans#412

Open
Krisztian F (krisztianfekete) wants to merge 3 commits into
agent-substrate:mainfrom
krisztianfekete:feat/actor-identity-spans
Open

feat(otel): set ate.* actor telemetry identity on ateapi spans#412
Krisztian F (krisztianfekete) wants to merge 3 commits into
agent-substrate:mainfrom
krisztianfekete:feat/actor-identity-spans

Conversation

@krisztianfekete

@krisztianfekete Krisztian F (krisztianfekete) commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR sets consistent, namespaced actor-identity set on the spans we already emit:

  • ate.atespace, ate.actor.id, ate.actor.template.{name,namespace}, ate.actor.version
  • ateapi: the RPC server span for create/resume/suspend/pause/delete

We do this, so platform traces are queryable by actor, atespace, and template. Cross-hop correlation and per-tenant/per-template filtering, all on traces (not TSDB labels, as discussed in #174). This is a general, workload-neutral telemetry identity plumbing. We could add gen_ai specific stuff on top of this later.

This is how golden snapshots get build:
image

This is a run:
image

Notes:

  • Left as it was on purpose: metric attribute names and the stdout log labels (ate.dev/*), but we should probably do this as well. Maybe in this PR?

  • Bikeshed is welcomed on the ate.* spelling before merge, renaming span attrs later is painful.

  • Tests pass

  • Appropriate changes to documentation are included in the PR

@krisztianfekete Krisztian F (krisztianfekete) changed the title feat(otel): set ate.* actor telemetry identity on router and ateapi spans feat(otel): set ate.* actor telemetry identity on ateapi spans Jul 9, 2026
@krisztianfekete Krisztian F (krisztianfekete) marked this pull request as ready for review July 10, 2026 15:59
Comment thread internal/ateattr/ateattr_test.go Outdated
return m
}

func TestActorIdentity(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please rewrite test based on data driven pattern?

tests := []struct {....}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
....

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done!

}
}

func TestCreateActor_StampsFullSpanIdentity(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the "cmd/ateapi/internal/controlapi/span_identity_test.go" supposed to concentrate on methods exposed in the cmd/ateapi/internal/controlapi/span_identity.go:

  • setSpanActorIdentity
  • setSpanActorRefIdentity

The "TestCreateActor_StampsFullSpanIdentity" method needs to be tested in create_actor_test.go. that does not exist today :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I split it up.

if err := validatePauseActorRequest(req); err != nil {
return nil, err
}
setSpanActorRefIdentity(ctx, req.GetActor().GetAtespace(), req.GetActor().GetName())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity, what will be happened if instead of setSpanActorRefIdentity, the "setSpanActorIdentity" will be called.
The only value that is changing between line 33 to 46 is version.
I guess the intention is to keep the latest version right? Is it possible to call to setSpanActorIdentity twice. before s.actorWorkflow.PauseActor and after? The latest version will be submitted in successful case and previous version will be submitted in case of error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't call the full one early. At line 33 we only have the ObjectRef from the request and setSpanActorIdentity needs the resolved *Actor that we don't have until the workflow returns it.

Attributes overwrite by key, so the later wins and we get the latest version on success. On error we return early with only the ref, so no version.

I guess we could also add the version on mid-workflow failures if we move it inside the workflow. Maybe that's better as a follow-up if it's worth it. What do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 without looking at the code, it's not obvious which "version" is in the attribute (is it the version before or after the pause?) we can update the key to "new_version" or "updated_version" for clarification.

Image

@dberkov

Copy link
Copy Markdown
Collaborator
  • Left as it was on purpose: metric attribute names and the stdout log labels (ate.dev/*), but we should probably do this as well. Maybe in this PR?
  • Bikeshed is welcomed on the ate.* spelling before merge, renaming span attrs later is painful.

What do you mean by it?

@krisztianfekete

Copy link
Copy Markdown
Contributor Author
  • Left as it was on purpose: metric attribute names and the stdout log labels (ate.dev/*), but we should probably do this as well. Maybe in this PR?
  • Bikeshed is welcomed on the ate.* spelling before merge, renaming span attrs later is painful.

What do you mean by it?

  • This PR only covers span attributes. I left the stdout log labels and metric attribute names as-is to keep the scope small. Would you prefer to do the reconsiliation in this PR, or as a follow-up?
  • The ate.* bikeshed was just about deciding on the namespace before merge. I haven't seen an explicit decision on ate.* formally (might have just missed it), so wanted to bring it up.

Thanks for the review!


// setSpanActorRefIdentity is setSpanActorIdentity for the identity subset known
// before the Actor record resolves, so a failed lookup still carries who/where.
func setSpanActorRefIdentity(ctx context.Context, atespace, actorID string) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the parameter name is actorID but the callsites are passing in actor name, can we change it to actorName?

// setSpanActorRefIdentity is setSpanActorIdentity for the identity subset known
// before the Actor record resolves, so a failed lookup still carries who/where.
func setSpanActorRefIdentity(ctx context.Context, atespace, actorID string) {
trace.SpanFromContext(ctx).SetAttributes(ateattr.ActorRefIdentity(atespace, actorID)...)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here, can we rename ActorRefIdentity to ActorObjectReference to be consistent with pkg/proto/ateapipb/ateapi.proto

}

// ActorIdentity is nil-safe; a nil Actor yields zero-valued attributes.
func ActorIdentity(a *ateapipb.Actor) []attribute.KeyValue {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this should be ActorAttributes since it logs several fields that I would not consider as the "identity" of the actor.

})

assertSpanStr(t, attrs, ateattr.AtespaceKey, testAtespace)
assertSpanStr(t, attrs, ateattr.ActorIDKey, "id1")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: also create constants for "id" similar to testAtespace above?

if err := validatePauseActorRequest(req); err != nil {
return nil, err
}
setSpanActorRefIdentity(ctx, req.GetActor().GetAtespace(), req.GetActor().GetName())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 without looking at the code, it's not obvious which "version" is in the attribute (is it the version before or after the pause?) we can update the key to "new_version" or "updated_version" for clarification.

Image

// Dotted ate.* matches the metric-instrument naming (atenet.*, atelet.*), not the
// ate.dev/ slash form used for k8s labels and stdout log fields.
const (
AtespaceKey = attribute.Key("ate.atespace")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ate.actor.atespace?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test in pause_actor_test.go?

"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
)

// installSpanRecorder swaps in a recording global TracerProvider. Span-producing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have each test create its own trace provider to avoid this global provider shared by all tests? We will want to run tests in parallel later to speed up testing.

@zoez7

Copy link
Copy Markdown
Collaborator

While reviewing this I realized today the atenet router uses different keys as trace attribute
https://github.com/agent-substrate/substrate/blob/c1ab0958f85202faf9f87ea66cd98903a9de763b/cmd/atenet/internal/router/resumer.go#L49-L50C22

and the traces are not connected with the ateapi one, for the same ResumeActor request, we have one trace which traces the request through AteAPI-Atelet -Ateom
Image

but another, detached one with just the atenet router:
Image

We don't need to address it in this PR. Could you help us create a tracking issue for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants