From 32b8f84f15afeab0e3e658765e17c9fcad4ab655 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 16 Oct 2026 22:46:05 -0400 Subject: [PATCH 1/3] feat(protos): let an agent declare what it offers and cap throughput on the policy plane Catalog export of an agent card had no source for offered skills, media types, or interaction capabilities, and nothing capped a single session's spend or request rate. Both gaps are closed where ADR#0025 places them: the offer and execution subject on the revision, ceilings on the policy plane. Signed-off-by: Yordis Prieto --- .../0025-agent-definition-data-ownership.md | 3 + ...offered-interface-and-throughput-limits.md | 229 ++++++++++++++++++ docs/adr/index.md | 1 + docs/glossary/agentconfiguration.md | 4 +- .../agents/agents/v1alpha1/agent.proto | 7 + .../agents/agents/v1alpha1/interface.proto | 140 +++++++++++ .../agent_profiles/v1alpha1/profile.proto | 7 + .../v1alpha1/set_agent_profile.proto | 2 +- .../v1alpha1/budget_policy.proto | 8 + .../v1alpha1/agent_rate_limit_cleared.proto | 14 ++ .../agent_rate_limit_initialized.proto | 23 ++ .../v1alpha1/agent_rate_limit_set.proto | 15 ++ .../v1alpha1/clear_agent_rate_limit.proto | 21 ++ .../agent_rate_limits/v1alpha1/events.proto | 20 ++ .../agent_rate_limits/v1alpha1/faults.proto | 49 ++++ .../initialize_agent_rate_limit.proto | 31 +++ .../v1alpha1/rate_limit_policy.proto | 96 ++++++++ .../v1alpha1/set_agent_rate_limit.proto | 24 ++ 18 files changed, 692 insertions(+), 2 deletions(-) create mode 100644 docs/adr/0070-agent-offered-interface-and-throughput-limits.md create mode 100644 proto/trogonai/agents/agents/v1alpha1/interface.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_cleared.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_initialized.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_set.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/clear_agent_rate_limit.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/events.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/faults.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/initialize_agent_rate_limit.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto create mode 100644 proto/trogonai/policy/agent_rate_limits/v1alpha1/set_agent_rate_limit.proto diff --git a/docs/adr/0025-agent-definition-data-ownership.md b/docs/adr/0025-agent-definition-data-ownership.md index 68d7f017bc..7a221956db 100644 --- a/docs/adr/0025-agent-definition-data-ownership.md +++ b/docs/adr/0025-agent-definition-data-ownership.md @@ -153,6 +153,9 @@ AgentConfiguration required_memory_declarations + optional_memory_declarations (selectors by kind, never an instance reference) selectable_labels + interface + (advertised skills, default media types, interaction capabilities) + execution_subject Proposal proposal_id diff --git a/docs/adr/0070-agent-offered-interface-and-throughput-limits.md b/docs/adr/0070-agent-offered-interface-and-throughput-limits.md new file mode 100644 index 0000000000..6179c57b28 --- /dev/null +++ b/docs/adr/0070-agent-offered-interface-and-throughput-limits.md @@ -0,0 +1,229 @@ +--- +number: "0070" +slug: agent-offered-interface-and-throughput-limits +status: draft +date: 2026-10-16 +--- + +# ADR#0070: An Agent Declares What It Offers and Whose Authority It Carries; Throughput Ceilings Stay on the Policy Plane + +## Context + +[ADR#0025](./0025-agent-definition-data-ownership.md) admits a datum into +[AgentConfiguration](../glossary/agentconfiguration) only when it is the Agent's +own behavior or dependency declaration, every session pinned to the revision +should begin with it, and changing it should invalidate behavioral +verification. Everything the configuration holds today looks inward: runtime +settings, skill pins, and tool, delegate, memory, and connection declarations +under [ADR#0062](./0062-runtime-owned-settings-and-platform-declarations.md) +and [ADR#0063](./0063-agent-connection-declarations.md) all describe what the +Agent needs. Nothing describes what it offers. + +That gap blocks a decision already made. +[ADR#0012](./0012-ard-compatible-discovery-catalog.md) allows an +[A2A](../glossary/a2a) agent card to be exported as a catalog entry with media +type `application/a2a-agent-card+json`. The +[A2A specification](https://a2a-protocol.org/latest/specification/) requires a +card to list the skills the agent offers, its default input and output modes, +and its capabilities, including streaming, push notifications, and state +transition history. The card's name and description have sources in the Agent +and its configuration. Its skills, modes, and capabilities have no source +anywhere in the schema, so an export would have to invent them. + +A second inward gap concerns whose authority a run carries. Some Agents act +only as themselves. Others exist to act for an authenticated human, and a +session that starts without one is a different run, not the same run with less +context. Nothing in the configuration says which kind of Agent it is, so +admission cannot reject a session that starts in the wrong mode. + +The policy plane has its own gap. `policy/agent_budgets` caps money per Agent +over a calendar month or a lifetime. An agentic loop can make unbounded model +calls inside one session, and the platform has no ceiling on a single session's +spend and no throughput control at all: no token or request ceiling over a +window and no cap on how many model calls one session may make. +[ADR#0025](./0025-agent-definition-data-ownership.md) Decision 5 already lists +"budget, token, turn, concurrency, and delegation ceilings" among the data that +never enters an AgentConfiguration, and +[ADR#0062](./0062-runtime-owned-settings-and-platform-declarations.md) gives +budgets their own control plane. The question is only where the new ceilings +live on that plane. + +The [agent platform research index](../research/agent-platform/index.md) +records the products studied for the definition, session, and policy split this +record extends. + +## Decision + +### 1. `AgentConfiguration.interface` declares what the Agent offers + +`AgentConfiguration` gains `AgentInterface interface`. An `AgentInterface` +carries: + +- `offered_skills`, each an `OfferedSkill` with a stable `skill_key`, a `name`, + a `description`, `tags`, `examples`, and optional per-skill input and output + media type overrides that otherwise inherit the interface defaults; +- `default_input_media_types` and `default_output_media_types`; and +- `InteractionCapabilities`, with `streaming`, `push_notifications`, and + `state_transition_history`. + +The interface passes every statement of the +[ADR#0025](./0025-agent-definition-data-ownership.md) ownership test. It is the +Agent's own declaration of behavior. Every session pinned to the revision +begins with the same offer, and session admission validates inbound and +outbound parts against it. Changing it changes what callers may ask for and +what they get back, which invalidates behavioral verification and requires a +new revision. + +It is the protocol-neutral source from which the catalog layer of +[ADR#0012](./0012-ard-compatible-discovery-catalog.md) derives an A2A card. +The interface is not A2A-shaped on purpose, consistent with that record's rule +that the neutral model must not reuse A2A identifiers as its domain. + +An `OfferedSkill` is the opposite direction from `SkillDeclaration`. A +`SkillDeclaration` pins content the Agent consumes; an `OfferedSkill` +advertises an ability callers consume. Advertising grants no authority and +resolves no dependency. + +An absent interface means the Agent offers no external interface and cannot be +exported as a callable catalog entry. + +The interface deliberately omits the remaining card fields, because each +already has an owner: + +- endpoints and transports are platform bindings under + [ADR#0016](./0016-protobuf-rpc-over-nats-micro-binding.md); +- security schemes belong to + [ADR#0017](./0017-aauth-agent-authentication.md); +- card signatures are produced at export with the Agent's own key under + [ADR#0036](./0036-agent-self-certifying-identity.md), so no stored signature + can drift from the card it signs; and +- the provider organization derives from the hierarchy `parent`, which + [ADR#0025](./0025-agent-definition-data-ownership.md) assigns to the + hierarchy plane. + +### 2. `AgentConfiguration.execution_subject` declares whose authority a session carries + +`AgentConfiguration` gains `ExecutionSubject execution_subject`, with the +values `AGENT_ONLY`, `DELEGATED_PRINCIPAL_ONLY`, and +`AGENT_OR_DELEGATED_PRINCIPAL`. Absent means `AGENT_ONLY`. + +Whether a run carries a human principal changes what the Agent does and what +verification must cover, so the declaration is configuration-owned. Session +admission enforces it: a session that does not match the declared subject is +rejected, and under `AGENT_OR_DELEGATED_PRINCIPAL` the session records which +mode applied. + +The declaration grants no authority. It names no principal and does not say +who may invoke the Agent, which +[ADR#0025](./0025-agent-definition-data-ownership.md) keeps on authentication +and policy. The identity binding itself stays on the identity plane under +[ADR#0036](./0036-agent-self-certifying-identity.md) and +[ADR#0037](./0037-agent-identity-governance.md). The configuration says only +which binding a session must present. + +### 3. `AgentBudgetPeriod.SESSION` caps money per Session + +`policy/agent_budgets` gains `AGENT_BUDGET_PERIOD_SESSION` beside the calendar +month and lifetime periods. It caps all spending attributable to one Session +from its start through its terminal state. Spending a Session delegates to +descendant Sessions counts against its root. Each Session's counter starts at +zero and is never shared with another Session. + +A per-session money ceiling is policy, not configuration: it is a funder's +stance, it changes at the funder's cadence, and changing it must not mint a +revision. It therefore joins the existing budget policy rather than the +revision. Admission evaluates it in the same conjunction as the other periods, +and it neither resets nor replaces their counters. + +### 4. `policy/agent_rate_limits` adds throughput ceilings + +A new policy module `policy/agent_rate_limits` holds an +`AgentRateLimitPolicy` per Agent. The policy carries: + +- `AgentRateLimit` entries, each keyed by a scope (`AGENT` or `SESSION`) and a + meter (`TOKENS` or `MODEL_REQUESTS`), with a `maximum` and a fixed `window`; + and +- an `AgentSessionCallLimit`, a cap on total model requests per Session with no + window. + +The module follows the shape of `policy/agent_budgets`: initialize, set, and +clear commands with matching events, and the same role as a provisioning +participant. Counters live in authoritative accounting history and never in +the policy stream, so setting, clearing, or replaying a policy cannot reset +them. + +The platform had money ceilings only. Throughput is a separate axis, because a +cheap model can still exhaust an upstream quota or run a loop indefinitely, and +[ADR#0025](./0025-agent-definition-data-ownership.md) and +[ADR#0062](./0062-runtime-owned-settings-and-platform-declarations.md) already +place ceilings on the policy plane. A separate module keeps money and +throughput independently settable, since they are often owned by different +people. + +### 5. `AgentProfile.documentation_url` is presentation data + +The catalog profile gains `documentation_url`, which A2A cards also carry. It +accepts only credential-free absolute HTTPS URLs, under the same admission rule +as `Avatar.https_url`. + +It belongs on the profile, not the revision. A documentation link changes +nothing any session does, so it fails the +[ADR#0025](./0025-agent-definition-data-ownership.md) verification test, and +updating it must not require a proposal. + +### 6. Rejected or deferred + +- **Guardrail bindings.** No guardrail resource exists for a binding to + reference. A binding without a target is an unverifiable string. +- **A paused but not archived lifecycle state.** Pausing changes the registry + lifecycle, which is a separate lifecycle decision rather than an interface or + limit decision. +- **An operational kill-switch webhook.** Stopping a running Agent belongs to a + runtime or session plane, and an outbound webhook only matters when the + platform proxies an externally hosted Agent. +- **Per-agent trace-enforcement toggles.** Trace context is already mandatory + under [ADR#0042](./0042-nats-trace-context-and-message-path-tracing.md). A + toggle could only weaken a platform guarantee. +- **Pricing fields.** What a caller pays belongs to the settlement plane, not to + the Agent's offer or to its spending ceilings. + +## Consequences + +- An Agent without an interface is not exportable as a callable catalog entry. + Export is a consequence of a declaration, never a default. +- Offered skill keys are unique within an interface and stable across + revisions, so a caller or catalog can follow one skill through the Agent's + history. +- A session-scoped budget or rate limit never shares counters between + Sessions, and a root Session's budget counter includes its descendants. +- Each scope and meter pair appears at most once in an `AgentRateLimitPolicy`. +- An absent budget or rate limit policy removes only that Agent-specific + ceiling. It is never a zero cap and never bypasses another scope's limit or + an authorization check. +- Configuration digests minted before this change remain valid, since the new + fields are additive at fresh tags and absent values serialize to zero bytes. +- The catalog export layer of + [ADR#0012](./0012-ard-compatible-discovery-catalog.md) must map + `AgentInterface`, the Agent's name and description, and + `AgentProfile.documentation_url` to the A2A card shape, and must produce the + card's endpoints, security, signatures, and provider from their own planes. +- Session admission must enforce `execution_subject` and must validate parts + against the interface's media types, including per-skill overrides. Until it + does, both fields are reviewable declarations without enforcement. +- Authoritative accounting must attribute spend, tokens, and model requests by + Session, including descendant Sessions under their root, before the session + period or the session scope can admit or reject anything. + +## References + +- [ADR#0012: ARD-Compatible Discovery Catalog](./0012-ard-compatible-discovery-catalog.md) +- [ADR#0016: Protocol Buffers RPC over NATS micro Binding](./0016-protobuf-rpc-over-nats-micro-binding.md) +- [ADR#0017: AAuth Agent Authentication over a Trogon NATS PoP Binding](./0017-aauth-agent-authentication.md) +- [ADR#0025: Agent Definition Data Ownership](./0025-agent-definition-data-ownership.md) +- [ADR#0036: Agent Self-Certifying Cryptographic Identity](./0036-agent-self-certifying-identity.md) +- [ADR#0037: Agent Identity Governance](./0037-agent-identity-governance.md) +- [ADR#0042: NATS Trace Context and Message Path Tracing](./0042-nats-trace-context-and-message-path-tracing.md) +- [ADR#0062: Runtime-Owned Settings and Platform Declarations](./0062-runtime-owned-settings-and-platform-declarations.md) +- [ADR#0063: An Agent Declares the Connections It Needs](./0063-agent-connection-declarations.md) +- [Agent2Agent (A2A) specification](https://a2a-protocol.org/latest/specification/) +- [Agent platform research index](../research/agent-platform/index.md) diff --git a/docs/adr/index.md b/docs/adr/index.md index 9db45069d5..343f030dbe 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -75,3 +75,4 @@ future implementation work. - [ADR#0067: A Session Binds to a Checkout, Not a Workspace](./0067-session-checkout-not-workspace.md) - [ADR#0068: A Reference Field Says It Is a Reference in Both Its Name and Its Type](./0068-reference-fields-say-they-are-references.md) - [ADR#0069: Every Event Envelope Records Who Caused It and When Each Step Happened (Draft)](./0069-event-envelope-records-issuer-and-times.md) +- [ADR#0070: An Agent Declares What It Offers and Whose Authority It Carries; Throughput Ceilings Stay on the Policy Plane (Draft)](./0070-agent-offered-interface-and-throughput-limits.md) diff --git a/docs/glossary/agentconfiguration.md b/docs/glossary/agentconfiguration.md index 96b9749c2d..7e6eedeb0b 100644 --- a/docs/glossary/agentconfiguration.md +++ b/docs/glossary/agentconfiguration.md @@ -14,5 +14,7 @@ declarations, such as skill pins and memory dependencies, whose use requires support from the selected runtime or adapter. The configuration digest commits to all three: the runtime binding, including its implementation release and definition digest; the settings payload; and every revision-owned platform -declaration. See draft +declaration. The configuration also carries the offered interface (advertised +skills, default media types, and interaction capabilities) and the execution +subject, both revision-owned. See draft [ADR#0062](../adr/0062-runtime-owned-settings-and-platform-declarations.md). diff --git a/proto/trogonai/agents/agents/v1alpha1/agent.proto b/proto/trogonai/agents/agents/v1alpha1/agent.proto index 5373b1efdf..5e1e0ad02a 100644 --- a/proto/trogonai/agents/agents/v1alpha1/agent.proto +++ b/proto/trogonai/agents/agents/v1alpha1/agent.proto @@ -6,6 +6,7 @@ import "buf/validate/validate.proto"; import "google/protobuf/any.proto"; import "trogonai/agents/agents/v1alpha1/dependencies.proto"; import "trogonai/agents/agents/v1alpha1/implementation.proto"; +import "trogonai/agents/agents/v1alpha1/interface.proto"; import "trogonai/agents/agents/v1alpha1/variables.proto"; // AgentConfiguration owns immutable behavior and dependency declarations. @@ -39,4 +40,10 @@ message AgentConfiguration { // Selection consumes the labels of the pinned revision. Annotation changes // cannot change this set or which declarations a session starts with. map selectable_labels = 7; + // Absent means the Agent offers no external interface and cannot be exported + // as a callable catalog entry. Sessions validate parts against it. + AgentInterface interface = 8; + // Absent means AGENT_ONLY. Session admission enforces the declared subject; + // the declaration itself grants no authority over any principal. + ExecutionSubject execution_subject = 9 [(buf.validate.field).enum.defined_only = true]; } diff --git a/proto/trogonai/agents/agents/v1alpha1/interface.proto b/proto/trogonai/agents/agents/v1alpha1/interface.proto new file mode 100644 index 0000000000..2b1e7b644d --- /dev/null +++ b/proto/trogonai/agents/agents/v1alpha1/interface.proto @@ -0,0 +1,140 @@ +edition = "2024"; + +package trogonai.agents.agents.v1alpha1; + +import "buf/validate/validate.proto"; + +// One ability this Agent advertises to callers and catalogs. This is the +// opposite direction from SkillDeclaration, which pins content the Agent +// consumes. Advertising grants no authority and resolves no dependency. +message OfferedSkill { + // Stable within this Agent across revisions. Keys are case-sensitive and + // validation does not trim or normalize them. + string skill_key = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).cel = { + id: "offered_skill.skill_key_nonblank" + message: "a skill key must contain a non-whitespace character" + expression: "this.trim() != ''" + } + ]; + string name = 2 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).cel = { + id: "offered_skill.name_nonblank" + message: "a skill name must contain a non-whitespace character" + expression: "this.trim() != ''" + } + ]; + string description = 3 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).cel = { + id: "offered_skill.description_nonblank" + message: "a skill description must contain a non-whitespace character" + expression: "this.trim() != ''" + } + ]; + // Values are exact, case-sensitive, and unique. Search may index these tags, + // but runtime selectors never consume them. + repeated string tags = 4 [(buf.validate.field).repeated = { + unique: true + items: { + cel: { + id: "offered_skill.tags_nonblank" + message: "a skill tag must contain a non-whitespace character" + expression: "this.trim() != ''" + } + } + }]; + repeated string examples = 5 [(buf.validate.field).repeated.items.cel = { + id: "offered_skill.examples_nonblank" + message: "a skill example must contain a non-whitespace character" + expression: "this.trim() != ''" + }]; + // Optional per-skill overrides of the interface defaults. An empty list + // inherits the corresponding AgentInterface default. + repeated string input_media_types = 6 [(buf.validate.field).repeated = { + unique: true + items: { + cel: { + id: "offered_skill.input_media_types_nonblank" + message: "a media type must contain a non-whitespace character" + expression: "this.trim() != ''" + } + } + }]; + repeated string output_media_types = 7 [(buf.validate.field).repeated = { + unique: true + items: { + cel: { + id: "offered_skill.output_media_types_nonblank" + message: "a media type must contain a non-whitespace character" + expression: "this.trim() != ''" + } + } + }]; +} + +// Interaction behaviors the Agent commits to for every session pinned to the +// revision. Presence of this message is the declaration; each flag is a +// behavior the runtime or adapter must support, otherwise admission rejects. +message InteractionCapabilities { + bool streaming = 1; + bool push_notifications = 2; + bool state_transition_history = 3; +} + +// What the Agent offers to callers: the external, protocol-neutral surface a +// catalog exports, for example as an A2A card under ADR#0012, and a session +// validates inbound and outbound parts against. It is revision-owned: changing +// it invalidates behavioral verification and requires a new revision. It is +// not an endpoint, a transport, a security scheme, or a grant; those bind on +// other planes (ADR#0016, ADR#0017). +message AgentInterface { + option (buf.validate.message).cel = { + id: "agent_interface.skill_keys_unique" + message: "offered skill keys must be unique" + expression: "this.offered_skills.map(s, s.skill_key).unique()" + }; + + repeated OfferedSkill offered_skills = 1; + // Media types in RFC 6838 form, such as text/plain or application/json. At + // least one input and one output media type is required. + repeated string default_input_media_types = 2 [(buf.validate.field).repeated = { + min_items: 1 + unique: true + items: { + cel: { + id: "agent_interface.default_input_media_types_nonblank" + message: "a media type must contain a non-whitespace character" + expression: "this.trim() != ''" + } + } + }]; + repeated string default_output_media_types = 3 [(buf.validate.field).repeated = { + min_items: 1 + unique: true + items: { + cel: { + id: "agent_interface.default_output_media_types_nonblank" + message: "a media type must contain a non-whitespace character" + expression: "this.trim() != ''" + } + } + }]; + InteractionCapabilities interaction = 4 [features.field_presence = LEGACY_REQUIRED]; +} + +// Whose authority a session carries. Unspecified and unknown values are +// invalid. Absent on AgentConfiguration means AGENT_ONLY. +enum ExecutionSubject { + EXECUTION_SUBJECT_UNSPECIFIED = 0; + // Sessions act as the Agent itself and never carry a delegating principal. + EXECUTION_SUBJECT_AGENT_ONLY = 1; + // Every session acts on behalf of an authenticated principal and must carry + // one. + EXECUTION_SUBJECT_DELEGATED_PRINCIPAL_ONLY = 2; + // Each session is admitted in either mode; the session records which mode + // applied. + EXECUTION_SUBJECT_AGENT_OR_DELEGATED_PRINCIPAL = 3; +} diff --git a/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto b/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto index fe235d579b..d980251682 100644 --- a/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto +++ b/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto @@ -30,4 +30,11 @@ message AgentProfile { Avatar avatar = 2; CategoryReference category = 3; repeated DiscoveryTag discovery_tags = 4; + // Where callers read about this Agent. Presentation only; absent or empty + // means none. Only credential-free absolute HTTPS URLs are durable profile + // data, under the same admission rule as Avatar.https_url. + string documentation_url = 5 [(buf.validate.field).string = { + uri: true + pattern: "(?i)^https://(?:\\[[0-9a-f:.]+\\]|[^:/?#@\\[\\]]+)(?::[0-9]+)?(?:[/?#].*)?$" + }]; } diff --git a/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto b/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto index 04dbb3012a..83fb26596c 100644 --- a/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto +++ b/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto @@ -5,7 +5,7 @@ package trogonai.catalog.agent_profiles.v1alpha1; import "trogonai/catalog/agent_profiles/v1alpha1/profile.proto"; // Replaces an initialized profile without changing its Agent identity. Absent -// avatar/category and empty tags clear those values. Dispatch authorizes the +// avatar/category and empty tags or documentation URL clear those values. Dispatch authorizes the // writer, applies initialization's content checks, and requires authoritative // current ACTIVE lifecycle plus DecideRequest.command_id and expected_revision. // Pending setup cannot be replaced through this normal mutation path; cancelled diff --git a/proto/trogonai/policy/agent_budgets/v1alpha1/budget_policy.proto b/proto/trogonai/policy/agent_budgets/v1alpha1/budget_policy.proto index 86e0bcef47..6ef519d995 100644 --- a/proto/trogonai/policy/agent_budgets/v1alpha1/budget_policy.proto +++ b/proto/trogonai/policy/agent_budgets/v1alpha1/budget_policy.proto @@ -14,6 +14,10 @@ enum AgentBudgetPeriod { AGENT_BUDGET_PERIOD_CALENDAR_MONTH_UTC = 1; // All spending attributable to this Agent, including before policy creation. AGENT_BUDGET_PERIOD_LIFETIME = 2; + // All spending attributable to one Session from its start through its + // terminal state, including spending it delegates to descendant Sessions. + // Each Session's counter starts at zero; it is never shared across Sessions. + AGENT_BUDGET_PERIOD_SESSION = 3; } message AgentBudgetLimit { @@ -49,6 +53,10 @@ message AgentBudgetLimit { // Authoritative registry readiness gates execution and new spending while setup // is pending or cancelled; an absent policy is never a shortcut around that gate. // +// A session period caps each Session independently. Admission evaluates it +// alongside the calendar and lifetime periods as one conjunction; it neither +// resets nor replaces their counters. +// // Hard-cap admission atomically accounts for incurred spend, remaining reserved // liability, and the new operation's maximum liability in every applicable // period. Settled billing totals alone cannot authorize spending. Unbounded or diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_cleared.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_cleared.proto new file mode 100644 index 0000000000..debcd930ea --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_cleared.proto @@ -0,0 +1,14 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; + +// Removes the policy, never the identity or previously counted and reserved +// requests. An already cleared history emits no second clear. +message AgentRateLimitCleared { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_initialized.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_initialized.proto new file mode 100644 index 0000000000..ee71eed651 --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_initialized.proto @@ -0,0 +1,23 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto"; + +// Genesis fixes the Agent identity for this history. Reinitializing after a +// clear cannot reset a counter or open a fresh window. The admitted origin and +// exact initial policy remain immutable receipt evidence after replacements and +// clears, independent of the current policy projection. +message AgentRateLimitInitialized { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; + AgentRateLimitPolicy policy = 2 [features.field_presence = LEGACY_REQUIRED]; + string provisioning_id = 3 [(buf.validate.field).cel = { + id: "agent_rate_limit_initialized.provisioning_id_nonblank" + message: "a supplied provisioning_id must contain a non-whitespace character" + expression: "this.trim() != ''" + }]; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_set.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_set.proto new file mode 100644 index 0000000000..3601eccf4c --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_set.proto @@ -0,0 +1,15 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto"; + +// Replaces policy without replacing its accounting history. +message AgentRateLimitSet { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; + AgentRateLimitPolicy policy = 2 [features.field_presence = LEGACY_REQUIRED]; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/clear_agent_rate_limit.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/clear_agent_rate_limit.proto new file mode 100644 index 0000000000..d4384f3d74 --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/clear_agent_rate_limit.proto @@ -0,0 +1,21 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; + +// Clears only the Agent-specific ceiling. The initialized identity and +// accounting history remain. Other applicable policies still constrain +// requests. Dispatch requires live authority and a nonempty Decider command_id +// plus the original positive expected_revision; retries preserve both. An +// already cleared policy succeeds without an event after that concurrency +// check. Authoritative current registry lifecycle must be ACTIVE or ARCHIVED. +// Pending, cancelled, or unknown lifecycle rejects clearing, so requested +// provisioning limits cannot disappear before activation. Genesis provisioning +// origin remains. +message ClearAgentRateLimit { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/events.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/events.proto new file mode 100644 index 0000000000..86e773595a --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/events.proto @@ -0,0 +1,20 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_cleared.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_initialized.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/agent_rate_limit_set.proto"; + +// An unset or unknown event rejects replay; skipping a policy change could +// admit requests against a ceiling that no longer applies. +message AgentRateLimitEvent { + oneof event { + option (buf.validate.oneof).required = true; + + AgentRateLimitInitialized agent_rate_limit_initialized = 1; + AgentRateLimitSet agent_rate_limit_set = 2; + AgentRateLimitCleared agent_rate_limit_cleared = 3; + } +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/faults.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/faults.proto new file mode 100644 index 0000000000..ffa77272c8 --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/faults.proto @@ -0,0 +1,49 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "trogon/error/v1alpha1/options.proto"; + +// Schema-only templates for the policy.agent_rate_limits module's +// DeciderService refusals. These messages are never encoded. Administration +// authorization and request admission keep their own failure contracts; this +// fold only owns policy mutations, not reservation or counting outcomes. +message AgentRateLimitCommandInvalidError { + option (trogon.error.v1alpha1.message).template = { + domain: "policy.agent_rate_limits" + reason: "AGENT_RATE_LIMIT_COMMAND_INVALID" + message: "The command violates the Agent rate limit policy contract." + code: FAILED_PRECONDITION + visibility: VISIBILITY_PUBLIC + }; +} + +message AgentRateLimitNotInitializedError { + option (trogon.error.v1alpha1.message).template = { + domain: "policy.agent_rate_limits" + reason: "AGENT_RATE_LIMIT_NOT_INITIALIZED" + message: "The Agent rate limit history must be initialized before this command." + code: FAILED_PRECONDITION + visibility: VISIBILITY_PUBLIC + }; +} + +message AgentRateLimitAlreadyInitializedError { + option (trogon.error.v1alpha1.message).template = { + domain: "policy.agent_rate_limits" + reason: "AGENT_RATE_LIMIT_ALREADY_INITIALIZED" + message: "The Agent rate limit history has already been initialized." + code: FAILED_PRECONDITION + visibility: VISIBILITY_PUBLIC + }; +} + +message AgentRateLimitIdentityMismatchError { + option (trogon.error.v1alpha1.message).template = { + domain: "policy.agent_rate_limits" + reason: "AGENT_RATE_LIMIT_IDENTITY_MISMATCH" + message: "The command names a different Agent from this rate limit history." + code: FAILED_PRECONDITION + visibility: VISIBILITY_PUBLIC + }; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/initialize_agent_rate_limit.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/initialize_agent_rate_limit.proto new file mode 100644 index 0000000000..0e2643388d --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/initialize_agent_rate_limit.proto @@ -0,0 +1,31 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto"; + +// Dispatch verifies the Agent exists and live rate-limit administration +// authority. Authoritative current registry lifecycle must be ACTIVE or +// ARCHIVED for ordinary initialization. During PROVISIONING only its owning +// operation may initialize a requested rate limit; dispatch verifies registry +// origin, immutable setup intent, and exact policy rather than trusting +// caller-supplied provenance. PROVISIONING_CANCELLED and unknown lifecycle +// reject. The PREPARE path requires provisioning_id; a supplied origin is valid +// only while its operation is preparing this participant. NoStream creation +// requires a nonempty Decider command_id and no expected_revision; retries +// preserve the id. Initialization creates policy history, never a new counting +// window. An already-initialized rejection is not success. Recovery verifies +// the durable genesis receipt's Agent, provisioning origin, and exact policy. +message InitializeAgentRateLimit { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; + AgentRateLimitPolicy policy = 2 [features.field_presence = LEGACY_REQUIRED]; + string provisioning_id = 3 [(buf.validate.field).cel = { + id: "initialize_agent_rate_limit.provisioning_id_nonblank" + message: "a supplied provisioning_id must contain a non-whitespace character" + expression: "this.trim() != ''" + }]; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto new file mode 100644 index 0000000000..f233d469cb --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto @@ -0,0 +1,96 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "google/protobuf/duration.proto"; + +// Which counter the ceiling applies to. Unknown and unspecified values reject. +enum AgentRateLimitScope { + AGENT_RATE_LIMIT_SCOPE_UNSPECIFIED = 0; + // All Sessions of this Agent together. + AGENT_RATE_LIMIT_SCOPE_AGENT = 1; + // Each Session independently, with descendant Sessions counted with their + // root. + AGENT_RATE_LIMIT_SCOPE_SESSION = 2; +} + +// What is counted. Unknown and unspecified values reject. +enum AgentRateLimitMeter { + AGENT_RATE_LIMIT_METER_UNSPECIFIED = 0; + // Prompt plus completion tokens of model requests the Agent makes. + AGENT_RATE_LIMIT_METER_TOKENS = 1; + // Model requests the Agent makes. + AGENT_RATE_LIMIT_METER_MODEL_REQUESTS = 2; +} + +message AgentRateLimit { + AgentRateLimitScope scope = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).enum = { + defined_only: true + not_in: [0] + } + ]; + AgentRateLimitMeter meter = 2 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).enum = { + defined_only: true + not_in: [0] + } + ]; + // Exact count admitted inside one window. Zero forbids new requests. + uint64 maximum = 3 [features.field_presence = LEGACY_REQUIRED]; + // Fixed window length of at most one day. Admission attributes a request to + // the window open when it is authorized, not when it completes. + google.protobuf.Duration window = 4 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).duration = { + gt: {} + lte: {seconds: 86400} + } + ]; +} + +// Total model requests one Session may make across its lifetime, including +// requests it delegates to descendant Sessions. A ceiling against unbounded +// agentic loops; it is not a window. +message AgentSessionCallLimit { + uint32 maximum_model_requests = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).uint32.gt = 0 + ]; +} + +// Limits are an unordered conjunction: every configured limit and the session +// call limit must admit a new request. An absent policy removes only this +// Agent-specific ceiling, never another scope's limit or an authorization +// check. It does not mean a zero cap. Absence cannot satisfy rate-limit setup +// requested by a provisioning operation. +// +// Delegated requests retain ancestor Agent rate-limit scopes in their +// authorization provenance, including scopes without a configured policy at +// dispatch. Detach does not remove inherited scopes. One request counts once +// against each applicable scope, never again merely because several scopes +// constrain it. +// +// Counters belong to the authoritative accounting history, not this policy +// stream. Setting, clearing, restoring, archiving, changing Agent revisions, or +// replaying cannot reset them. Requests made while a policy is absent still +// count when it returns. A window boundary never refunds an in-flight +// reservation. +message AgentRateLimitPolicy { + option (buf.validate.message).cel = { + id: "agent_rate_limit_policy.limits_unique" + message: "each scope and meter pair may appear at most once" + expression: "this.limits.map(limit, string(limit.scope) + '/' + string(limit.meter)).unique()" + }; + option (buf.validate.message).cel = { + id: "agent_rate_limit_policy.nonempty" + message: "the policy must configure a limit or a session call limit" + expression: "this.limits.size() > 0 || has(this.session_call_limit)" + }; + + repeated AgentRateLimit limits = 1; + AgentSessionCallLimit session_call_limit = 2; +} diff --git a/proto/trogonai/policy/agent_rate_limits/v1alpha1/set_agent_rate_limit.proto b/proto/trogonai/policy/agent_rate_limits/v1alpha1/set_agent_rate_limit.proto new file mode 100644 index 0000000000..965231ed55 --- /dev/null +++ b/proto/trogonai/policy/agent_rate_limits/v1alpha1/set_agent_rate_limit.proto @@ -0,0 +1,24 @@ +edition = "2024"; + +package trogonai.policy.agent_rate_limits.v1alpha1; + +import "buf/validate/validate.proto"; +import "trogonai/policy/agent_rate_limits/v1alpha1/rate_limit_policy.proto"; + +// Replaces an initialized Agent's policy, including after a clear. Dispatch +// requires live administration authority and a nonempty Decider command_id plus +// original positive expected_revision. Retries preserve both; a refreshed +// revision is a new intent. Equal limits by scope and meter and an equal +// session call limit succeed without an event after the concurrency check; +// reordering limits alone is not a change. Accounting history is untouched. +// Authoritative current registry lifecycle must be ACTIVE or ARCHIVED. Pending, +// cancelled, or unknown lifecycle rejects normal replacement, preserving +// required provisioning setup. Genesis provisioning origin survives every +// replacement. +message SetAgentRateLimit { + string agent_id = 1 [ + features.field_presence = LEGACY_REQUIRED, + (buf.validate.field).string.min_len = 1 + ]; + AgentRateLimitPolicy policy = 2 [features.field_presence = LEGACY_REQUIRED]; +} From f5d11a909f5e200ab22b1850d4afa28b5a17ff93 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Fri, 16 Oct 2026 23:03:32 -0400 Subject: [PATCH 2/3] fix(protos): reject malformed media types and an explicit unspecified execution subject Catalog export would publish whatever string the interface carried, and an explicitly unspecified subject slipped past a rule that only checked the value was defined. An empty documentation URL now reads as invalid rather than as a clear, matching the avatar URL rule it borrows. Signed-off-by: Yordis Prieto --- .../agents/agents/v1alpha1/agent.proto | 10 ++++-- .../agents/agents/v1alpha1/interface.proto | 34 ++++++------------- .../agent_profiles/v1alpha1/profile.proto | 7 ++-- .../v1alpha1/set_agent_profile.proto | 2 +- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/proto/trogonai/agents/agents/v1alpha1/agent.proto b/proto/trogonai/agents/agents/v1alpha1/agent.proto index 5e1e0ad02a..0c6fb207e5 100644 --- a/proto/trogonai/agents/agents/v1alpha1/agent.proto +++ b/proto/trogonai/agents/agents/v1alpha1/agent.proto @@ -43,7 +43,11 @@ message AgentConfiguration { // Absent means the Agent offers no external interface and cannot be exported // as a callable catalog entry. Sessions validate parts against it. AgentInterface interface = 8; - // Absent means AGENT_ONLY. Session admission enforces the declared subject; - // the declaration itself grants no authority over any principal. - ExecutionSubject execution_subject = 9 [(buf.validate.field).enum.defined_only = true]; + // Absent means AGENT_ONLY; an explicit unspecified value is invalid. Session + // admission enforces the declared subject; the declaration itself grants no + // authority over any principal. + ExecutionSubject execution_subject = 9 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; } diff --git a/proto/trogonai/agents/agents/v1alpha1/interface.proto b/proto/trogonai/agents/agents/v1alpha1/interface.proto index 2b1e7b644d..38d79d75d7 100644 --- a/proto/trogonai/agents/agents/v1alpha1/interface.proto +++ b/proto/trogonai/agents/agents/v1alpha1/interface.proto @@ -51,26 +51,19 @@ message OfferedSkill { message: "a skill example must contain a non-whitespace character" expression: "this.trim() != ''" }]; - // Optional per-skill overrides of the interface defaults. An empty list - // inherits the corresponding AgentInterface default. + // Optional per-skill overrides of the interface defaults, in the same + // type/subtype form. An empty list inherits the corresponding AgentInterface + // default. repeated string input_media_types = 6 [(buf.validate.field).repeated = { unique: true items: { - cel: { - id: "offered_skill.input_media_types_nonblank" - message: "a media type must contain a non-whitespace character" - expression: "this.trim() != ''" - } + string: {pattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$"} } }]; repeated string output_media_types = 7 [(buf.validate.field).repeated = { unique: true items: { - cel: { - id: "offered_skill.output_media_types_nonblank" - message: "a media type must contain a non-whitespace character" - expression: "this.trim() != ''" - } + string: {pattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$"} } }]; } @@ -98,28 +91,21 @@ message AgentInterface { }; repeated OfferedSkill offered_skills = 1; - // Media types in RFC 6838 form, such as text/plain or application/json. At - // least one input and one output media type is required. + // Media types in RFC 6838 type/subtype form without parameters, such as + // text/plain or application/json. At least one input and one output media + // type is required. repeated string default_input_media_types = 2 [(buf.validate.field).repeated = { min_items: 1 unique: true items: { - cel: { - id: "agent_interface.default_input_media_types_nonblank" - message: "a media type must contain a non-whitespace character" - expression: "this.trim() != ''" - } + string: {pattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$"} } }]; repeated string default_output_media_types = 3 [(buf.validate.field).repeated = { min_items: 1 unique: true items: { - cel: { - id: "agent_interface.default_output_media_types_nonblank" - message: "a media type must contain a non-whitespace character" - expression: "this.trim() != ''" - } + string: {pattern: "^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}$"} } }]; InteractionCapabilities interaction = 4 [features.field_presence = LEGACY_REQUIRED]; diff --git a/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto b/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto index d980251682..f0ebff71ce 100644 --- a/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto +++ b/proto/trogonai/catalog/agent_profiles/v1alpha1/profile.proto @@ -30,9 +30,10 @@ message AgentProfile { Avatar avatar = 2; CategoryReference category = 3; repeated DiscoveryTag discovery_tags = 4; - // Where callers read about this Agent. Presentation only; absent or empty - // means none. Only credential-free absolute HTTPS URLs are durable profile - // data, under the same admission rule as Avatar.https_url. + // Where callers read about this Agent. Presentation only; absent means none + // and an empty value is invalid. Only credential-free absolute HTTPS URLs + // are durable profile data, under the same admission rule as + // Avatar.https_url. string documentation_url = 5 [(buf.validate.field).string = { uri: true pattern: "(?i)^https://(?:\\[[0-9a-f:.]+\\]|[^:/?#@\\[\\]]+)(?::[0-9]+)?(?:[/?#].*)?$" diff --git a/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto b/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto index 83fb26596c..4f1ebb77ac 100644 --- a/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto +++ b/proto/trogonai/catalog/agent_profiles/v1alpha1/set_agent_profile.proto @@ -5,7 +5,7 @@ package trogonai.catalog.agent_profiles.v1alpha1; import "trogonai/catalog/agent_profiles/v1alpha1/profile.proto"; // Replaces an initialized profile without changing its Agent identity. Absent -// avatar/category and empty tags or documentation URL clear those values. Dispatch authorizes the +// avatar, category, or documentation URL and empty tags clear those values. Dispatch authorizes the // writer, applies initialization's content checks, and requires authoritative // current ACTIVE lifecycle plus DecideRequest.command_id and expected_revision. // Pending setup cannot be replaced through this normal mutation path; cancelled From 1e59b677086ca46c35b2a8eb6265856c3123213f Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Sat, 17 Oct 2026 00:12:22 -0400 Subject: [PATCH 3/3] fix(protos): stop spelling an unresolved skill label as a registry handle Signed-off-by: Yordis Prieto --- .../0070-agent-offered-interface-and-throughput-limits.md | 8 +++++--- proto/trogonai/agents/agents/v1alpha1/interface.proto | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/adr/0070-agent-offered-interface-and-throughput-limits.md b/docs/adr/0070-agent-offered-interface-and-throughput-limits.md index 6179c57b28..f2fe4495cf 100644 --- a/docs/adr/0070-agent-offered-interface-and-throughput-limits.md +++ b/docs/adr/0070-agent-offered-interface-and-throughput-limits.md @@ -59,9 +59,11 @@ record extends. `AgentConfiguration` gains `AgentInterface interface`. An `AgentInterface` carries: -- `offered_skills`, each an `OfferedSkill` with a stable `skill_key`, a `name`, - a `description`, `tags`, `examples`, and optional per-skill input and output - media type overrides that otherwise inherit the interface defaults; +- `offered_skills`, each an `OfferedSkill` with a stable `skill_key`, a + `display_name` that nothing resolves per + [ADR#0040](./0040-contract-field-vocabulary.md), a `description`, `tags`, + `examples`, and optional per-skill input and output media type overrides + that otherwise inherit the interface defaults; - `default_input_media_types` and `default_output_media_types`; and - `InteractionCapabilities`, with `streaming`, `push_notifications`, and `state_transition_history`. diff --git a/proto/trogonai/agents/agents/v1alpha1/interface.proto b/proto/trogonai/agents/agents/v1alpha1/interface.proto index 38d79d75d7..f122858422 100644 --- a/proto/trogonai/agents/agents/v1alpha1/interface.proto +++ b/proto/trogonai/agents/agents/v1alpha1/interface.proto @@ -18,11 +18,13 @@ message OfferedSkill { expression: "this.trim() != ''" } ]; - string name = 2 [ + // A label for humans and catalogs. Nothing resolves it; skill_key is the + // stable handle. + string display_name = 2 [ features.field_presence = LEGACY_REQUIRED, (buf.validate.field).cel = { - id: "offered_skill.name_nonblank" - message: "a skill name must contain a non-whitespace character" + id: "offered_skill.display_name_nonblank" + message: "a skill display name must contain a non-whitespace character" expression: "this.trim() != ''" } ];