Summary
LoadInfo.attributes and RuntimeEvent.attributes in observability.proto use map<string, string>, which is lossy: every value is coerced to a string, so the value type (int / float / bool) and structure (list / nested object) are erased. Consumers (planners / controllers) must re-parse each key by an out-of-band convention, and structured values have to be re-encoded as JSON strings inside the proto.
This is also inconsistent with the schema's other open-ended fields, which already use google.protobuf.Struct:
Affected fields
| Location |
Current |
Proposed |
observability.proto — LoadInfo.attributes = 30 |
map<string, string> |
google.protobuf.Struct |
observability.proto — RuntimeEvent.attributes = 4 |
map<string, string> |
google.protobuf.Struct |
These carry mixed-type telemetry (counts, ratios, flags, lists) — exactly the case where Struct preserves value types and map<string,string> flattens them.
Why it matters
Example RuntimeEvent attributes as they'd naturally be produced:
queue_depth = 42 (integer)
utilization = 0.87 (float)
is_prefill = true (bool)
batch_ids = [1,2,3] (list)
With map<string, string> these become "42", "0.87", "true", "[1,2,3]" — every consumer re-implements parsing, bool/number encoding is ambiguous, and lists/nested objects require JSON-in-a-string (double encoding). Struct (which is map<string, google.protobuf.Value>) preserves the JSON value types directly.
Options
A. In-place type change (pre-adoption only):
google.protobuf.Struct attributes = 30; // LoadInfo, was map<string, string>
google.protobuf.Struct attributes = 4; // RuntimeEvent, was map<string, string>
Wire-breaking (a field type change), so only safe while the schema is pre-adoption at revision 1. buf breaking will correctly flag it.
B. New field + deprecate (safe post-adoption):
map<string, string> attributes = 30 [deprecated = true];
google.protobuf.Struct attributes_struct = 31;
Additive; both coexist during migration, old field reserved later.
Caveat
Struct numbers are IEEE-754 doubles (exact only to 2^53), so large int64 telemetry (e.g. cumulative token counters) must be carried as strings or dedicated typed fields — the schema already documents this pattern for KvSessionRef.attributes_struct. See protobuf#7643 and the ProtoJSON format docs.
Recommendation
Since the schema is still pre-adoption at revision 1, Option A is the cheap moment to make it consistent. Deferred from #23 because that PR is strictly additive.
Summary
LoadInfo.attributesandRuntimeEvent.attributesinobservability.protousemap<string, string>, which is lossy: every value is coerced to a string, so the value type (int / float / bool) and structure (list / nested object) are erased. Consumers (planners / controllers) must re-parse each key by an out-of-band convention, and structured values have to be re-encoded as JSON strings inside the proto.This is also inconsistent with the schema's other open-ended fields, which already use
google.protobuf.Struct:EngineError.details—StructKvSessionRef.attributes_struct—StructGenerateRequest.extra/TaskRequestContext.extra/ServerInfo.extra/ModelInfo.extra—Struct(added in feat(api): add engine-specificextraextension field for smoother adoption #23)Affected fields
observability.proto—LoadInfo.attributes = 30map<string, string>google.protobuf.Structobservability.proto—RuntimeEvent.attributes = 4map<string, string>google.protobuf.StructThese carry mixed-type telemetry (counts, ratios, flags, lists) — exactly the case where
Structpreserves value types andmap<string,string>flattens them.Why it matters
Example
RuntimeEventattributes as they'd naturally be produced:With
map<string, string>these become"42","0.87","true","[1,2,3]"— every consumer re-implements parsing, bool/number encoding is ambiguous, and lists/nested objects require JSON-in-a-string (double encoding).Struct(which ismap<string, google.protobuf.Value>) preserves the JSON value types directly.Options
A. In-place type change (pre-adoption only):
Wire-breaking (a field type change), so only safe while the schema is pre-adoption at revision 1.
buf breakingwill correctly flag it.B. New field + deprecate (safe post-adoption):
Additive; both coexist during migration, old field reserved later.
Caveat
Structnumbers are IEEE-754 doubles (exact only to 2^53), so largeint64telemetry (e.g. cumulative token counters) must be carried as strings or dedicated typed fields — the schema already documents this pattern forKvSessionRef.attributes_struct. See protobuf#7643 and the ProtoJSON format docs.Recommendation
Since the schema is still pre-adoption at revision 1, Option A is the cheap moment to make it consistent. Deferred from #23 because that PR is strictly additive.