Skip to content

[Feature Proposal] Support for Labeling and Filtering Substrate Entities #186

Open

Description

Currently, the Actor entity in Agent Substrate lacks support for arbitrary key-value metadata (labels/tags). As the platform grows, users and operators need a flexible way to categorize, group, and query active workloads without forcing database-specific indexing implementation requirements.

We propose adding a map<string, string> labels field to the Actor resource, enabling Kubernetes-style label selectors for filtering during ListActors requests.

Note

Performance Characteristic: ListActors is an operational control-plane query (used by command-line tools like kubectl-ate, admin UIs, and operator scripts) and is not on the latency-critical path for routing, scheduling, suspending, or resuming actors. Consequently, in-memory filtering at the service layer is acceptable and does not impact critical path latency.


User Stories & Use Cases

1. Ownership & Multi-Tenancy

  • As a developer using a shared Substrate cluster, I want to filter actor listings to only see my own workloads (e.g. owner=alice).
  • As a team lead, I want to list all actors billed to my team (e.g. team=checkout).

2. Environment & Resource Lifecycle Management

  • As an operator, I want to run a cleanup script that lists all actors in staging (e.g. env=staging) and deletes/suspends them at the end of the day, without affecting production workloads.

3. Business & Workflow Context

  • As a platform engineer, I want to look up an active agent instance by the customer id it is serving (e.g. customer_id=12345) to inspect its status or troubleshoot a support ticket.

4. Scheduler Placement constraints (Future proofing)

  • As a scheduler developer, I want actors to carry constraints (e.g. gpu=required, region=us-central) so that the orchestrator can schedule them onto matching worker pods.

Proposed Design

1. Protobuf API Changes

We will update pkg/proto/ateapipb/ateapi.proto to include a labels map in the Actor message and the creation requests, along with a string-based label_selector for query filtering.

message Actor {
  ...
  // Labels associated with the actor.
  map<string, string> labels = 12;
}

message CreateActorRequest {
  ...
  // Optional labels to apply to the actor upon creation.
  map<string, string> labels = 4;
}

message ListActorsRequest {
  ...
  // An optional Kubernetes-style label selector query string (e.g. "env=production,app=billing").
  string label_selector = 3;
}

2. Database-Agnostic Filtering

To keep the persistence layer pluggable and simple:

  • The Database Interface (store.Interface) will remain simple and only handle standard paginated key scanning. It will not know about the filter criteria.
  • The Service Layer (controlapi.ListActors) will fetch the page of raw actors and apply the filter evaluation in memory using the Go k8s.io/apimachinery/pkg/labels package.
  • Since our Redis storage driver uses protojson for marshaling/unmarshaling, the new labels field will automatically persist to Redis without requiring schema changes or updates to Redis Lua scripts.

Implementation Plan

  1. Update pkg/proto/ateapipb/ateapi.proto and re-generate Go protobuf bindings.
  2. Update the persistence layer interface and implementations to support setting and retrieving labels.
  3. Integrate k8s.io/apimachinery/pkg/labels at the service layer in cmd/ateapi/internal/controlapi/list_actors.go to filter list results before returning them.
  4. Add unit and integration tests covering label assignment, persistence, and selector querying.

Activity

  1. eliranw commented on Jun 8, 2026

    @eliranw
    Collaborator

    +1 from me, I’d use this. My case is plain metadata: tag actors with things like owner, app, or env and read it back on Get/List.

    One thing I noticed though: the proposal only sets labels at creation, on CreateActorRequest. There’s no way to change them afterward. That’s fine for some of this, but we might also need re-labeling on an already existing actor, maybe for promotion of an actor between envs, or attaching metadata to an existing actor.

    The Control service has no general update RPC today; actors only change through suspend, resume, and delete. So mutable labels would need a new RPC and a new write path, not just another field. Creation-only labels are basically free since protojson already persists them.

  2. dims commented on Jun 8, 2026

    @dims
    Collaborator

    Grant McCloskey (@MushuEE)

    • How does filtering interact with pagination/page_size? (Are we filtering after a fixed-size raw page? so page_size may not be number of results?)
    • user story 1: this is not a security boundary right? (alice can do owner=bob too)
    • any bounds on the count/size of labels? (grpc limits? redis limits?)
    • Guess we will want to update ActorTemplate controller to understand these as well?
    • for labels, should we do inequality and set based grammar as well?

    Also +1 to Mutability from eliranw above.

  3. added this to the Beta milestone on Jun 10, 2026
  4. thockin commented on Jun 10, 2026

    @thockin
    Collaborator

    I think this is mostly fine, but I want to ask a few questions before we commit to the shape of it:

    1. Can an ActorTemplate specify default labels? I guess that can be a distinct proposal, including which layer "wins" in case of conflict.

    2. ListActors is something we need to think REALLLLY carefully about. If we have 100M actors and each on is a few hundred bytes, that is going to be tens of gigabytes of data. We HAVE to do some sort of pagination and we need to be careful that selectors are not too complex to evaluate.

    3. We should spec the key and value format tightly. Probably just define it the same as k8s?

    4. If a selector is a string we need to define a minimal grammar -- is it JUST an "and" expression or can we specify "or" and other boolean operators. What about other operators that emerged in k8s (in, notin, exists, ...)? It's OK if we don't solve these now, but we should not just pretend these problems don't exist. And I think defining a grammar is better than defining an AST in JSON. :)

    5. We will need mutation of labels, almost certainly.

    6. This is not a replacement for Design: Should Agent Substrate have its own concept of “namespaces”? #21 (namespaces)

  5. thockin commented on Jun 10, 2026

    @thockin
    Collaborator

    FWIW I talked to Joe Betz (@jpbetz) about how he would retool k8s selectors if he could.

    His suggestion was to stay a subset of CEL, even if we don't use the CEL evaluator. Something like:

    ==, !=, x in [a, b, c], !(x in [a, b, c]), ||, &&

    label lookups would be something like labels["x"] == "value". By default CEL treats a failed map lookup as an error, but we could configure the map to think it has all keys, and just return and "undef" which is not equal to any value

  6. ptone commented on Oct 1, 2026

    @ptone

    Here is additional feedback and concrete integration findings from multi-tenant testing on Substrate d277088b regarding labels, annotations, and filtering:

    Summary

    Actors and ActorTemplates carry no labels, annotations or free-form metadata. Because of this, a client cannot record its own identity for an actor (tenant, owner, agent id) on the actor itself. Atespace and actor names are length-limited, so encoding identity into names is lossy. A client that keeps the mapping in memory loses it on restart, and every existing actor becomes unidentifiable.

    Impact

    • Who hits it: every integrator that maps its own objects onto Substrate actors.
    • What breaks:
      • After the integrator's control component restarts, it cannot tell which of its objects an existing actor belongs to.
      • In the tested integration, a delete after a restart could return not-found. The caller treated that as success, which orphaned the actor, its egress policy and its worker (verified live).
      • Per-actor credentials held by the integrator are lost the same way, and there is no Substrate-side channel to re-deliver them.
    • Cost: the integrator has to build and operate a separate durable mapping store, plus defensive logic for actors it cannot identify.
    • Severity: High / Proposed priority: P1. This is a basic building block for any multi-tenant integrator. The workarounds (an external durable store, refusing operations on unknown actors) are significant engineering, and the failure mode without them is silent orphaning.

    Environment

    • Substrate d277088b (v1alpha), ateapi proto.
    • Consequence observed on GKE 1.36 with gVisor workers.

    Steps to reproduce / evidence

    1. Where the fields are missing (code-read at d277088b, pkg/proto/ateapipb/ateapi.proto):
      • ResourceMetadata (from line 230) carries atespace, name, uid, version and timestamps.
      • Actor (from line 352) and ActorTemplate (from line 752) have no map field.
      • The only map<string, string> fields in the proto are:
        • Selector.match_labels (line 224);
        • a CSI volume_context (line 349);
        • Worker.labels, which mirror WorkerPool labels (line 1912).
      • None of them can be set on an actor or template.
    2. Consequence (verified live). The integrator kept the actor-to-object mapping in the memory of its control component and restarted that component with actors running. It could no longer attribute the existing actors. A delete for one of its objects returned not-found, and the actor, its egress policy and its worker were orphaned.

    Proposed behaviour / API sketch

    • Add map<string,string> labels and map<string,string> annotations to ResourceMetadata, or to Actor and ActorTemplate, with Kubernetes-style key and value validation. They should be settable at create and returned by Get and List.
    • Add a label selector to ListActors (and ListActorTemplates), for example label_selector: "tenant=abc,agent=xyz".
    • Optionally make labels mutable through an update RPC, so ownership can be corrected.

    Workaround

    An integrator can:

    • Keep a durable actor-to-object mapping outside Substrate.
    • Refuse destructive operations when it cannot attribute actors. When it finds actors in the target atespace that it has no record of (excluding actors already in DELETING), it can refuse delete and stop with an explicit "identity unknown" error rather than reporting not-found, and document manual operator cleanup.

    In the tested integration, roughly 400 lines of record-less actor detection and fail-closed handling exist only because of this gap.

  7. QuentinBisson commented on Oct 22, 2026

    @QuentinBisson
    Contributor

    We have a use case for this from the egress side. When an egress policy denies a destination, the agent's owners want to know which agent hit it, but the gateway only sees the actor name, which the creating platform (kagent) sets to a session id.

    Labels set at creation, for example an agent name, would let the gateways copy an allowlisted set of label keys onto their denial telemetry. We would not need to change labels after creation, and an ActorTemplate default would cover our case.

  8. QuentinBisson commented on Oct 26, 2026

    @QuentinBisson
    Contributor

    Grant McCloskey (@MushuEE) are you still on this? If not, we'd like to start with a small first step to be able to unlock our use case above

    • Actors get labels at creation, in Kubernetes format, with a total size cap. An ActorTemplate can set default labels; the actor's own value wins.
    • Labels can't be changed after creation. Changing them later needs its own permission (T-05), so that comes as a follow-up.
    • No selector on ListActors yet. A follow-up could use the CEL subset suggested above, run as a database query so pagination stays exact.
  9. QuentinBisson commented on Oct 26, 2026

    @QuentinBisson
    Contributor

    Here is a small PR that covers our need #3208

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

Metadata

Metadata

Labels

area/apiUser-facing API changesarea/api-machinerykind/featureAn enhancement / feature request or implementationprio/P1Important but not critical

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions