Skip to content

IRunAttributionContext

Namespace AgentPrism · Assembly AgentPrism.Abstractions.dll

Resolves who a run belongs to and what work it was made for.

public interface IRunAttributionContext

This is the sibling of ITenantContext. The tenant answers “whose data is this”; this interface answers “which user spent this” and “which job was it spent on”. Both questions are needed to break a cost report down further than the tenant.

The user identity is never read from the request body. A userId field on POST /api/agents/{name}/run would let any client write spend against another user’s name, which forges the cost record outright. The consumer binds this interface to its own identity pipeline — a claim, an API key, a resolved principal.

The interface is registered with TryAdd and the built-in implementation returns null for both members unless AmbientRunAttributionScope is active, so an application that registers nothing keeps its current behaviour exactly: the columns stay NULL and nothing else changes.

The implementation must be a singleton, for the same reason as ITenantContext: singleton services take a dependency on it, and a scoped registration would be a captive dependency. Resolve per-request state through IHttpContextAccessor.

Gets the labels of the current run, or null when there are none.

IReadOnlyDictionary<string, string>? Labels { get; }

IReadOnlyDictionary<string, string>?

Labels are a query dimension, not a metric dimension. They live in the runs table and are never added to agentprism.tokens or agentprism.run.cost: turning a free label set into a metric dimension blows up time-series cardinality. The same rule applies to IRunAttributionContext.UserId. The limits are on RunLabels.

Gets the user the current run belongs to, or null when it is unknown.

string? UserId { get; }

string?

The value is an opaque string. AgentPrism neither resolves nor validates its meaning, and stores no personal detail of its own; the consumer decides what the identifier means. This is the same stance IDataSubjectResolver takes, and the data subject erasure flow covers this column too.