Agent API
Table of Contents
upsquad/agent/v1/agent.proto
Agent
Agent represents an AI agent instance within the scope hierarchy.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the UUID of the agent. | |
| org_id | string | org_id is the tenant organisation UUID. | |
| pillar_id | string | pillar_id is the business unit UUID. | |
| team_id | string | team_id is the team UUID the agent belongs to. | |
| name | string | name is the human-readable agent name. | |
| clearance | int32 | clearance is the agent's clearance level (1-5). | |
| created_at | google.protobuf.Timestamp | created_at is when the agent was provisioned. | |
| deleted_at | google.protobuf.Timestamp | deleted_at is set when the agent is soft-deleted; null for active agents. | |
| is_headless | bool | is_headless indicates the agent was provisioned via RegisterAgent (API). | |
| status | string | status is the agent runtime status: "active" (default) or "paused". GAP-37 (#1085) — set by PauseAgent / ResumeAgent. Empty string is treated as "active" for forward-compat with rows persisted before migration 083 was applied. |
AgentConfiguration
AgentConfiguration holds per-agent configuration data.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the configuration record UUID. | |
| agent_id | string | agent_id is the UUID of the agent this configuration belongs to. | |
| persona | string | persona is the agent's system prompt / persona text. | |
| model_id | string | model_id is the LLM model identifier (e.g. "claude-sonnet-4-20250514"). | |
| tools | string | tools is the per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS the agent's team-resolved MCP set (ADR-0022 W3/B8, #1744). Empty/absent = inherit ALL team MCP servers. REPURPOSED from the legacy tool-definitions array (the static-list read is bypassed by the v2.4 resolver). | |
| config | string | config is a JSON-encoded map of arbitrary configuration. | |
| updated_at | google.protobuf.Timestamp | updated_at is the last modification timestamp. | |
| gateway_routed | bool | gateway_routed reports whether this agent's LLM traffic is routed through the Model Gateway — MG-4.1's per-agent opt-in (migration 240, issue #3143). |
It is READ-ONLY on this message. The only write path is SetAgentGatewayRouting, which carries its own authorisation; a field on the configuration a create/register call echoes back would be a second write surface for a governance switch, which is how the two drift.
It is RUNTIME-AGNOSTIC and it is NOT llm_base_url (field 10 of RegisterAgentRequest): that one stays an Anthropic-format claude_sdk endpoint override. Two meanings, two fields. |
AgentLLMEnablement
AgentLLMEnablement is one agent's NARROW-ONLY LLM selection (MG-2.6).
EMPTY MEANS NO NARROWING ON THAT AXIS, INDEPENDENTLY. This is the same empty=no-constraint rule agent_configurations.tools and llm_enablement already use (LLD #2801 section 3.2: "a source that is empty contributes no constraint; it never contributes permission"). The opposite reading — empty means nothing is permitted — looks fail-closed and is a trap: an operator who clears a selection through the product would brick the agent, and clearing is the operation this surface exists to make reversible.
A non-empty list is the ONLY set the agent may use on that axis. Because it is a NARROWING it can never widen past the team's approved set (gate 7c) or the spend cap (gate 7d) — those still bound the agent whatever is listed here.
| Field | Type | Label | Description |
|---|---|---|---|
| endpoint_ids | string | repeated | endpoint_ids are llm_endpoints UUIDs the agent may route to. Every entry must be an endpoint the agent's team holds an APPROVED GRANT binding for. |
| model_ids | string | repeated | model_ids are model identifiers the agent may request, matched EXACTLY (never by prefix — prefix matching is the endpoint-side semantics of llm_endpoints.model_ids and applying it here would silently widen a selection). When the team carries an approved model allowlist, every entry must be in it; when the team carries none, the team restricts no model and any entry is a legitimate narrowing. |
AgentTemplate
AgentTemplate is one entry in the prototype-template catalog backing the redesigned 4-step Deploy wizard. Fields mirror the wizard's step-1 card semantics.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the stable template identifier (e.g. "support-triage"). | |
| name | string | name is the short display name shown in the wizard card title. | |
| description | string | description is the one-paragraph summary shown under the name. | |
| icon | string | icon is a stable icon token the client maps to its icon registry (e.g. "lifebuoy", "receipt", "search", "wrench"). | |
| default_actions | string | repeated | default_actions is the seed list of actions the wizard pre-populates for this template. Free-form strings — the agent persona text synthesises them into a system prompt at deploy time. |
| best_for | string | best_for is the "use this when…" tagline shown on the card. |
BudgetStatus
BudgetStatus mirrors GetBudgetStatusResponse but is embedded inline on each ListAgentRow to avoid the N+1 the redesigned Agents directory would otherwise incur (one GetBudgetStatus per row). GAP-21 (#1084).
| Field | Type | Label | Description |
|---|---|---|---|
| daily_token_cap | int64 | daily_token_cap is the configured daily token limit (0 = unlimited). | |
| monthly_token_cap | int64 | monthly_token_cap is the configured monthly token limit (0 = unlimited). | |
| daily_cost_cap | double | daily_cost_cap is the configured daily cost limit in USD (0 = unlimited). | |
| monthly_cost_cap | double | monthly_cost_cap is the configured monthly cost limit in USD (0 = unlimited). | |
| daily_tokens_used | int64 | daily_tokens_used is the tokens consumed today. | |
| monthly_tokens_used | int64 | monthly_tokens_used is the tokens consumed this month. | |
| daily_cost_used | double | daily_cost_used is the cost incurred today in USD. | |
| monthly_cost_used | double | monthly_cost_used is the cost incurred this month in USD. | |
| status_color | string | status_color is a denormalised UI signal: "green" (well under cap), "amber" (>= 75% of any cap), or "red" (>= cap). Computed on the server from the same {used, cap} pairs so the client never has to re-derive the threshold. Empty when no row exists in agent_budgets (the agent has not had a budget configured yet). |
CreateAgentRequest
CreateAgentRequest provisions a new agent within the caller's scope.
| Field | Type | Label | Description |
|---|---|---|---|
| pillar_id | string | pillar_id is the OPTIONAL business unit to create the agent in. Empty for agents on root teams, which have no pillar (uniform team graph, #1851). | |
| team_id | string | team_id is the team to create the agent in. | |
| name | string | name is the human-readable agent name. | |
| clearance | int32 | clearance is the agent's clearance level (1-5). | |
| persona | string | persona is the optional initial persona text. | |
| model_id | string | model_id is the optional LLM model identifier; defaults to "claude-sonnet-4-20250514". | |
| tools | string | tools is the optional initial per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS the agent's team-resolved MCP set (ADR-0022 W3/B8, #1744). Empty/absent = inherit ALL team MCP servers. REPURPOSED from the legacy tool-definitions array. |
CreateAgentResponse
CreateAgentResponse confirms agent creation.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the newly created agent record. | |
| configuration | AgentConfiguration | configuration is the initial agent configuration. |
DeleteAgentRequest
DeleteAgentRequest soft-deletes an agent by ID.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent to soft-delete. |
DeleteAgentResponse
DeleteAgentResponse confirms the soft deletion.
| Field | Type | Label | Description |
|---|---|---|---|
| deleted_at | google.protobuf.Timestamp | deleted_at is the timestamp when the agent was marked as deleted. |
DeployAgentToTeamRequest
DeployAgentToTeamRequest assigns an agent to a team.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. | |
| team_id | string | team_id is the UUID of the target team. |
DeployAgentToTeamResponse
DeployAgentToTeamResponse confirms the deployment.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the updated agent record. | |
| deployed_at | google.protobuf.Timestamp | deployed_at is the timestamp of the deployment. |
DeployFromTemplateBudget
DeployFromTemplateBudget carries the budget caps for the wizard's budget step. Fields mirror SetBudgetCapRequest.
| Field | Type | Label | Description |
|---|---|---|---|
| daily_token_cap | int64 | daily_token_cap is the daily token limit (0 = unlimited). | |
| monthly_token_cap | int64 | monthly_token_cap is the monthly token limit (0 = unlimited). | |
| daily_cost_cap | double | daily_cost_cap is the daily cost limit in USD (0.0 = unlimited). | |
| monthly_cost_cap | double | monthly_cost_cap is the monthly cost limit in USD (0.0 = unlimited). |
DeployFromTemplateRequest
DeployFromTemplateRequest is the one-shot wizard commit. All fields are validated up front; on any failure the caller receives a single errdetails.BadRequest with FieldViolations pointing at the specific fields that need correction (LLD-4 §2.1 pattern). The composite is transactional — every sub-step (RegisterAgent + DeployAgentToTeam + SetBudgetCap + SetAutonomyLevel) commits atomically.
| Field | Type | Label | Description |
|---|---|---|---|
| template_id | string | template_id selects a row from ListAgentTemplates. Required. | |
| name | string | name is the human-readable agent name. Required. | |
| role | string | role is the free-text role tag rendered on the agent card (e.g. "Customer Support Tier-1"). Optional. | |
| reports_to | string | reports_to is the supervising member UUID — drives the inferred clearance via MemberService.InferAgentClearance. Required. | |
| team_id | string | team_id is the destination team (= org_unit of unit_type='team'). Required. | |
| pillar_id | string | pillar_id is the team's parent pillar — required by the legacy RegisterAgent contract until org-units fully replaces it. | |
| clearance | int32 | clearance is the agent's clearance level (1..5). The wizard pre-fills this from InferAgentClearance but the caller may override. Final value is still constrained by the supervising member's clearance via the existing CreateMember invariant. | |
| autonomy | AutonomyLevel | autonomy is the governance autonomy level the wizard's slider selects. Required. | |
| approval_template | string | approval_template is the JSON approval-template required when autonomy crosses the governance-review threshold. Optional otherwise. | |
| budget | DeployFromTemplateBudget | budget carries the daily/monthly token + cost caps from the wizard's budget step. | |
| guardrail_ids | string | repeated | guardrail_ids is the optional list of guardrail UUIDs to attach. Empty = no guardrails. Currently a forward-looking seam — the composite records the IDs in the lifecycle event metadata so the governance worker can pick them up; the actual attach RPC lives on a separate service (GAP-39, future). |
| model_id | string | model_id is the LLM model id; defaults to the platform default (claude-sonnet-4-20250514) when empty. | |
| persona | string | persona is the optional system prompt; the wizard synthesises one from default_actions when empty. | |
| tools | string | tools is the optional per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS the agent's team-resolved MCP set (ADR-0022 W3/B8, #1744). Empty/absent = inherit ALL team MCP servers; the wizard supplies an empty array when none are deselected. REPURPOSED from the legacy tool-definitions array. The wizard CANNOT grant — grants stay team-level. |
DeployFromTemplateResponse
DeployFromTemplateResponse returns the new agent's identity. Callers can fetch the full record via GetAgent if they need the persona / tools / budget back.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the newly-deployed agent. | |
| created_at | google.protobuf.Timestamp | created_at is the timestamp the agent row was inserted. | |
| applied_clearance | int32 | applied_clearance is the final clearance level persisted on the agent — surfaced so the wizard can render the "applied X (capped by manager)" hint without a follow-up call. |
GetAgentHealthRequest
GetAgentHealthRequest reads real-time health signals for an agent.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. |
GetAgentHealthResponse
GetAgentHealthResponse returns real-time health data.
| Field | Type | Label | Description |
|---|---|---|---|
| status | string | status is the derived health status: "healthy", "degraded", "down", or "idle". | |
| last_active | google.protobuf.Timestamp | last_active is the timestamp of the agent's most recent action. | |
| error_rate | double | error_rate is the percentage of errors in the recent window (0-100). | |
| queue_depth | int64 | queue_depth is the number of pending items in the agent's work queue. | |
| tokens_used_24h | int64 | tokens_used_24h is the total tokens consumed in the last 24 hours. |
GetAgentLLMEnablementRequest
GetAgentLLMEnablementRequest reads an agent's selection and its ceiling.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent (agents.id). |
GetAgentLLMEnablementResponse
GetAgentLLMEnablementResponse carries the selection and the ceiling.
| Field | Type | Label | Description |
|---|---|---|---|
| selection | AgentLLMEnablement | selection is the agent's current narrow-only selection. Both lists empty means the agent narrows nothing and is bounded by its team alone. | |
| available_endpoint_ids | string | repeated | available_endpoint_ids is the ENDPOINT CEILING: every llm_endpoints id the agent's team holds an approved GRANT binding for, on a live endpoint. An EMPTY list means the team has been granted no endpoint — so no non-empty endpoint selection is legal, which is not the same as "no constraint". |
This axis is an enumerated GRANT and its emptiness therefore means "nothing granted", the OPPOSITE of what an empty selection means. The two emptiness rules sit on the same message on purpose; see model_selection_restricted for the other one. |
| available_model_ids | string | repeated | available_model_ids is the MODEL CEILING: the union, over the endpoints above, of what each one SERVES narrowed by what the team has kept ENABLED (llm_endpoint_units.enabled_models, migration 262, core#3601).
It is DERIVED FROM THE ENDPOINT AXIS, which is the #3601 change. Until then it was the team's status='approved' entries in an ORG-WIDE model catalogue, unrelated to the endpoints the team was bound to — so the two axes on this message could each admit things the other made unusable.
Entries may be PREFIXES when an endpoint claims one and the team has not narrowed it (LLD #2673 §8.3m). A prefix here is honest: it is what the team may select, and every id under it is admitted.
MEANINGLESS UNLESS model_selection_restricted is true. | | model_selection_restricted | bool | | model_selection_restricted reports whether a team model restriction is in force at all. False means the team holds no approved grant binding on a live endpoint, available_model_ids is empty, AND every model is a legal selection — the incumbent direct-dial path, which consults no binding until 5E6.
A client MUST read this flag rather than deriving it from the list being empty — deriving it is a second implementation of the empty=allow-all rule, and it is the one that drifts. |
GetAgentRequest
GetAgentRequest retrieves a single agent by ID.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent to retrieve. |
GetAgentResponse
GetAgentResponse returns the agent and its configuration.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the retrieved agent record. | |
| configuration | AgentConfiguration | configuration is the agent's current configuration. |
GetBudgetStatusRequest
GetBudgetStatusRequest retrieves current-period usage vs caps.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. |
GetBudgetStatusResponse
GetBudgetStatusResponse returns current usage and caps.
| Field | Type | Label | Description |
|---|---|---|---|
| daily_token_cap | int64 | daily_token_cap is the configured daily token limit. | |
| monthly_token_cap | int64 | monthly_token_cap is the configured monthly token limit. | |
| daily_cost_cap | double | daily_cost_cap is the configured daily cost limit in USD. | |
| monthly_cost_cap | double | monthly_cost_cap is the configured monthly cost limit in USD. | |
| daily_tokens_used | int64 | daily_tokens_used is the tokens consumed today. | |
| monthly_tokens_used | int64 | monthly_tokens_used is the tokens consumed this month. | |
| daily_cost_used | double | daily_cost_used is the cost incurred today in USD. | |
| monthly_cost_used | double | monthly_cost_used is the cost incurred this month in USD. | |
| period_start | google.protobuf.Timestamp | period_start is the start of the current billing period. |
ListAgentRow
ListAgentRow is the rich shape returned by ListAgents — the bare
agents repeated field is preserved for backwards compatibility, but
new clients should consume rows instead. GAP-21 + GAP-36 (#1084).
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the canonical Agent record. | |
| budget | BudgetStatus | budget is the inline budget snapshot (avoids N+1 GetBudgetStatus calls on the directory page). Nil when no row exists in agent_budgets. | |
| owner_human_id | string | owner_human_id is the UUID of the supervising human member — resolved by walking the agent's member row up the reports_to chain to the first MEMBER_TYPE_HUMAN ancestor. Empty when the agent has no associated member row (legacy headless agents pre-migration 058) or when no human ancestor exists in the chain. | |
| owner_human_name | string | owner_human_name is the denormalised display_name of owner_human_id, surfaced inline so the directory does not need a second round-trip per row. | |
| last_action_at | google.protobuf.Timestamp | last_action_at is the timestamp of the agent's most recent recorded action — sourced from Redis (agent:<id>:last_active). Nil when no activity has ever been observed. | |
| actions_24h | int64 | actions_24h is the cardinality of distinct actions observed in the last 24 hours, sourced from Redis (agent:<id>:actions). | |
| team_deleted | bool | team_deleted is true when the agent's team_id references an org_unit that no longer resolves to a LIVE team (soft-deleted or nonexistent). Integrity #1813: migration 070 dropped the agents.team_id FK, so an agent can outlive the team it was bound to and be silently orphaned onto a tombstone. The directory MUST render these as "team deleted / unassigned" rather than resolving the tombstone's name as if live. False for agents with a NULL/empty team_id (legitimately org-wide / headless) and for agents bound to a live team. |
ListAgentTemplatesRequest
ListAgentTemplatesRequest is empty for v1 (no filters yet).
ListAgentTemplatesResponse
ListAgentTemplatesResponse returns the static template catalog.
| Field | Type | Label | Description |
|---|---|---|---|
| templates | AgentTemplate | repeated | templates is the ordered prototype-template catalog. |
ListAgentsRequest
ListAgentsRequest returns a paginated list of active agents.
All filter fields are optional and AND-combined. Legacy callers passing only pillar_id / team_id retain the original behaviour; new filters default to "no filter" so the response shape is unchanged when none are set.
| Field | Type | Label | Description |
|---|---|---|---|
| pillar_id | string | pillar_id optionally filters agents by business unit. | |
| team_id | string | team_id optionally filters agents by team. | |
| page | int32 | page is the 1-based page number for pagination. | |
| page_size | int32 | page_size is the maximum number of results per page. Max 100, default 20. | |
| clearance_filter | int32 | clearance_filter restricts results to a specific clearance level (1..5). 0 = no filter. GAP-35 (#1084). | |
| autonomy_filter | AutonomyLevel | autonomy_filter restricts results to a specific autonomy level. AUTONOMY_LEVEL_UNSPECIFIED = no filter. GAP-35. | |
| status_filter | string | status_filter restricts results to a specific status — "active" or "paused". Empty = no filter. GAP-35. | |
| health_filter | HealthFilter | health_filter restricts results to a derived health bucket; see HealthFilter for semantics. GAP-35. |
Implementation note for callers: OVER_BUDGET and UNDER_UTILIZED are applied as SQL predicates so total_count and the returned page are both filter-aware. RECENTLY_FAILING is a POST-FETCH filter applied in process against Redis-derived health state — the SQL layer returns the unfiltered page, then rows that don't match the recently-failing bucket are dropped before responding. As a result, the page size for a RECENTLY_FAILING request may shrink below page_size and total_count remains the pre-filter SQL count (the upper bound). See ListAgentsResponse.total_count for matching semantics on the response side. | | query | string | | query is a free-text substring matched case-insensitively against the agent name. Empty = no filter. GAP-35. |
ListAgentsResponse
ListAgentsResponse returns a paginated list of agents.
| Field | Type | Label | Description |
|---|---|---|---|
| agents | Agent | repeated | agents is the list of agent records (legacy field — kept for backwards compatibility with pre-redesign clients). |
| total_count | int32 | total_count is the total number of matching agents (for pagination). For OVER_BUDGET and UNDER_UTILIZED this reflects the SQL-side filtered count (matches the page size). For HEALTH_FILTER_RECENTLY_FAILING this is the pre-filter SQL count — an upper bound — because that bucket is computed post-fetch from Redis state; the rows / agents arrays may be shorter than the count for that filter only. | |
| rows | ListAgentRow | repeated | rows is the rich-shape list with budget inline + row metadata. New clients should consume this and ignore agents. GAP-21 + GAP-36. |
PauseAgentRequest
PauseAgentRequest transitions an agent to status=paused.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent to pause. |
PauseAgentResponse
PauseAgentResponse confirms the pause.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the updated agent record (status=paused). | |
| already_paused | bool | already_paused is true if the agent was already paused (idempotent no-op — no lifecycle event was emitted). |
RegisterAgentRequest
RegisterAgentRequest creates a headless agent with full configuration.
| Field | Type | Label | Description |
|---|---|---|---|
| pillar_id | string | pillar_id is the OPTIONAL business unit UUID. Empty for agents on root teams, which have no pillar (uniform team graph, #1851). | |
| team_id | string | team_id is the team UUID. | |
| name | string | name is the human-readable agent name. | |
| clearance | int32 | clearance is the agent's clearance level (1-5). | |
| persona | string | persona is the system prompt text (max 32000 chars). | |
| model_id | string | model_id is the LLM model id; defaults to claude-sonnet-4-20250514. | |
| tools | string | tools is the per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS this agent's team-resolved MCP set (ADR-0022 W3/B8, #1744). Empty/absent = inherit ALL of the team's approved MCP servers. It can only DESELECT — an id absent from the team set has no effect (never a grant). REPURPOSED from the legacy "JSON array of tool definitions": the legacy static-list read is BYPASSED by the Org Model v2.4 resolver, so no behaviour regression. Enforced at session freeze (freezeResolvedTools). | |
| autonomy_level | AutonomyLevel | autonomy_level is the governance mode. | |
| runtime | string | runtime selects the worker executor: "langgraph" (default) | |
| llm_base_url | string | llm_base_url is an optional Anthropic-format endpoint for the claude_sdk runtime. Only valid when runtime == "claude_sdk". Added in #1631. |
RegisterAgentResponse
RegisterAgentResponse confirms headless agent creation.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the newly created agent record. | |
| configuration | AgentConfiguration | configuration is the initial agent configuration. |
RegisterExternalAgentRequest
RegisterExternalAgentRequest registers a BYO agent and issues its credential. It provisions a first-class agent (runtime="external") governed identically to an internal agent (team, clearance, per-agent MCP allowlist, audit), then records the register-once credential→agent binding in external_agent_clients.
| Field | Type | Label | Description |
|---|---|---|---|
| pillar_id | string | pillar_id is the OPTIONAL business unit UUID. Empty for root-team agents. | |
| team_id | string | team_id is the team UUID; it is also the agent's unit_id (the token audience /mcp/team/{unit}). Required. | |
| name | string | name is the human-readable agent name. Required. | |
| clearance | int32 | clearance is the agent's clearance level (1-5). Required. | |
| persona | string | persona is the optional system prompt text (max 32000 chars). | |
| model_id | string | model_id is the optional LLM model id; defaults to claude-sonnet-4-20250514. | |
| tools | string | tools is the optional per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS the agent's team-resolved MCP set (ADR-0022 W3/B8). Empty = inherit ALL team MCP servers. | |
| autonomy_level | AutonomyLevel | autonomy_level is the optional governance mode. | |
| public_key_jwk | string | public_key_jwk is the customer's PUBLIC key as a JWK JSON object (the PREFERRED private_key_jwt path). When set, NO shared secret is generated and client_secret is empty in the response. When empty, the server generates a shared secret and returns it ONCE. Exactly one credential is issued. |
RegisterExternalAgentResponse
RegisterExternalAgentResponse returns the new agent + its client credential.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the newly created external agent record. | |
| configuration | AgentConfiguration | configuration is the initial agent configuration (runtime="external"). | |
| client_id | string | client_id is the server-generated credential handle the external agent presents to the token endpoint (PR-4). Stable for the life of the binding. | |
| client_secret | string | client_secret is the server-generated shared secret, returned EXACTLY ONCE. Populated ONLY for EXTERNAL_CREDENTIAL_TYPE_SHARED_SECRET; empty for the public-key path. It is never persisted in plaintext or logged. | |
| credential_type | ExternalCredentialType | credential_type indicates how the agent authenticates (public key vs secret). |
ResumeAgentRequest
ResumeAgentRequest transitions an agent back to status=active.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent to resume. |
ResumeAgentResponse
ResumeAgentResponse confirms the resume.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the updated agent record (status=active). | |
| already_active | bool | already_active is true if the agent was already active (idempotent no-op — no lifecycle event was emitted). |
RevokeExternalAgentCredentialRequest
RevokeExternalAgentCredentialRequest disables a client credential.
| Field | Type | Label | Description |
|---|---|---|---|
| client_id | string | client_id identifies the credential to revoke. Required. Org-scoped. |
RevokeExternalAgentCredentialResponse
RevokeExternalAgentCredentialResponse confirms the revocation.
| Field | Type | Label | Description |
|---|---|---|---|
| revoked_at | google.protobuf.Timestamp | revoked_at is the timestamp the credential was disabled. The agent is refused at its next token refresh (<=10 min). |
RotateExternalAgentCredentialRequest
RotateExternalAgentCredentialRequest rotates a client's credential in place.
| Field | Type | Label | Description |
|---|---|---|---|
| client_id | string | client_id identifies the credential to rotate. Required. Scoped to the caller's org (an explicit org_id predicate enforces ownership). | |
| public_key_jwk | string | public_key_jwk, when set, replaces the stored public key (private_key_jwt path). When empty, the server generates a NEW shared secret and returns it ONCE. The client_id and agent binding are unchanged; rotated_at is stamped. |
RotateExternalAgentCredentialResponse
RotateExternalAgentCredentialResponse returns the post-rotation credential.
| Field | Type | Label | Description |
|---|---|---|---|
| client_id | string | client_id is unchanged (rotation reuses the binding). | |
| client_secret | string | client_secret is the newly-generated shared secret, returned EXACTLY ONCE. Populated ONLY for the shared-secret path; empty for the public-key path. | |
| credential_type | ExternalCredentialType | credential_type indicates the post-rotation credential shape. | |
| rotated_at | google.protobuf.Timestamp | rotated_at is the timestamp the rotation was recorded. |
SetAgentGatewayRoutingRequest
SetAgentGatewayRoutingRequest flips one agent's Model Gateway routing opt-in.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent (agents.id). | |
| gateway_routed | bool | optional | gateway_routed is the REPLACEMENT value: true routes this agent's LLM traffic through the Model Gateway, false returns it to the direct path. |
optional IS LOAD-BEARING AND IS NOT DECORATION. A proto3 bool without presence has no distinguishable unset state, so "the client forgot the field" and "the operator opted this agent OUT" would be the SAME REQUEST on the wire — and the one that arrives by accident is the one that silently de-routes a live agent. With presence, an absent field is refused with InvalidArgument, exactly as SetAgentLLMEnablement refuses an absent selection rather than reading it as a clear.
This does NOT make opt-out a second action. Both directions are this RPC with this field set — true or false — under the same authorisation, and both emit the same audit event. #3060 constraint 1 asks that opt-out be the inverse of opt-in, not that an omitted field mean something. |
SetAgentGatewayRoutingResponse
SetAgentGatewayRoutingResponse confirms the routing update.
| Field | Type | Label | Description |
|---|---|---|---|
| gateway_routed | bool | gateway_routed is the value AS STORED, read back from the row rather than echoed from the request, so a write that landed something other than what was asked for is visible here rather than only in later traffic going somewhere nobody expected. | |
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
SetAgentLLMEnablementRequest
SetAgentLLMEnablementRequest replaces the agent's LLM enablement selection.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent (agents.id). | |
| selection | AgentLLMEnablement | selection is the REPLACEMENT selection. REQUIRED — an absent message is an InvalidArgument rather than a clear, for the same reason SetAgentTools rejects an empty tools string: "the client forgot the field" and "the operator cleared the selection" must not be the same request. |
A PRESENT selection with two empty lists IS the clear, and it is recorded as such: the stored value becomes an explicit empty object rather than SQL NULL, so "cleared by an operator" stays distinguishable from "never expressed" (migration 218's stated reason for having no DEFAULT). |
SetAgentLLMEnablementResponse
SetAgentLLMEnablementResponse confirms the enablement update.
| Field | Type | Label | Description |
|---|---|---|---|
| selection | AgentLLMEnablement | selection is the selection AS STORED, read back from the row rather than echoed from the request, so a client can see exactly what a subsequent gateway call will enforce. | |
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
SetAgentPersonaRequest
SetAgentPersonaRequest updates the persona text for an agent.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. | |
| persona | string | persona is the new persona text (max 32000 characters). |
SetAgentPersonaResponse
SetAgentPersonaResponse confirms the persona update.
| Field | Type | Label | Description |
|---|---|---|---|
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
SetAgentToolsRequest
SetAgentToolsRequest replaces the agent's per-agent MCP allowlist.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. | |
| tools | string | tools is the per-agent MCP allowlist: a JSON array of mcp_servers UUIDs that NARROWS the agent's team-resolved MCP set (ADR-0022 W3/B8, #1744). Empty array = clear the allowlist / inherit ALL team MCP servers. Every id must be a live MCP server owned by the caller's tenant. REPURPOSED from the legacy tool-definitions array. |
SetAgentToolsResponse
SetAgentToolsResponse confirms the tools update.
| Field | Type | Label | Description |
|---|---|---|---|
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
SetAutonomyLevelRequest
SetAutonomyLevelRequest configures agent governance autonomy.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. | |
| autonomy_level | AutonomyLevel | autonomy_level is the new governance level. | |
| approval_template | string | approval_template is an optional JSON approval template (for supervised/semi). |
SetAutonomyLevelResponse
SetAutonomyLevelResponse confirms the autonomy level update.
| Field | Type | Label | Description |
|---|---|---|---|
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
SetBudgetCapRequest
SetBudgetCapRequest sets daily/monthly token and cost limits.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent. | |
| daily_token_cap | int64 | daily_token_cap is the daily token limit (0 = unlimited). | |
| monthly_token_cap | int64 | monthly_token_cap is the monthly token limit (0 = unlimited). | |
| daily_cost_cap | double | daily_cost_cap is the daily cost limit in USD (0.0 = unlimited). | |
| monthly_cost_cap | double | monthly_cost_cap is the monthly cost limit in USD (0.0 = unlimited). |
SetBudgetCapResponse
SetBudgetCapResponse confirms the budget cap update.
| Field | Type | Label | Description |
|---|---|---|---|
| updated_at | google.protobuf.Timestamp | updated_at is the timestamp of the update. |
UpdateAgentRequest
UpdateAgentRequest modifies mutable agent fields.
PARTIAL BY IGNORE-IF-EMPTY, AND THERE IS DELIBERATELY NO FIELD MASK. Every
field here is "ignored when it carries its zero value", which is the
convention this message has used since it was written and which every client
of it already implements. Adding a mask for one new field would give the
message two partial-update conventions at once — and the field that follows
the newer one would be the field nobody's client sets correctly. The cost is
stated rather than hidden: a value cannot be CLEARED through this message.
model_id has no meaningful cleared state (the column is
TEXT NOT NULL DEFAULT), so the cost is not paid here; a field that DOES
need clearing is the change that should introduce the mask, for all fields at
once.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id is the UUID of the agent to update. | |
| name | string | name is the new agent name (ignored if empty). | |
| clearance | int32 | clearance is the new clearance level (ignored if zero). | |
| model_id | string | model_id is the new LLM model identifier for the agent (ignored if empty). |
It is VALIDATED AGAINST THE AGENT'S MODEL-ACCESS PRODUCT before it is written: the model must be served by an endpoint the agent's team holds an approved GRANT binding for, admitted by the team's model allowlist, and admitted by the agent's own narrow-only llm_enablement selection — the same three predicates the Model Gateway's routing and gates 7b/7c apply at call time. A model outside that product is refused InvalidArgument naming the model and the allowed set, because storing it would produce an agent that reads as configured and has every turn denied no_matching_endpoint (#3146 measured 214 consecutive such denials on beta-dev).
Setting this requires the SAME authority as every other governed agent_configurations write (L4-or-team-Manager), NOT the ungated authority name and clearance carry — see the RPC comment. |
UpdateAgentResponse
UpdateAgentResponse returns the updated agent.
| Field | Type | Label | Description |
|---|---|---|---|
| agent | Agent | agent is the updated agent record. |
AutonomyLevel
AutonomyLevel controls agent self-governance.
| Name | Number | Description |
|---|---|---|
| AUTONOMY_LEVEL_UNSPECIFIED | 0 | AUTONOMY_LEVEL_UNSPECIFIED is the default zero value. |
| AUTONOMY_LEVEL_SUPERVISED | 1 | AUTONOMY_LEVEL_SUPERVISED means every action requires approval. |
| AUTONOMY_LEVEL_SEMI_AUTONOMOUS | 2 | AUTONOMY_LEVEL_SEMI_AUTONOMOUS means only escalations need approval. |
| AUTONOMY_LEVEL_AUTONOMOUS | 3 | AUTONOMY_LEVEL_AUTONOMOUS means no approval required. |
ExternalCredentialType
ExternalCredentialType names how an external agent authenticates to the platform token endpoint (PR-4). It is DERIVED from which credential material is stored (public key XOR shared secret) — the server sets it on responses so the caller knows which secret handling applies.
| Name | Number | Description |
|---|---|---|
| EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED | 0 | EXTERNAL_CREDENTIAL_TYPE_UNSPECIFIED is the zero value; never persisted. |
| EXTERNAL_CREDENTIAL_TYPE_PUBLIC_KEY_JWT | 1 | EXTERNAL_CREDENTIAL_TYPE_PUBLIC_KEY_JWT is the PREFERRED path: the customer holds the private key and registered only the public JWK. Nothing secret is returned — the customer already holds the key. |
| EXTERNAL_CREDENTIAL_TYPE_SHARED_SECRET | 2 | EXTERNAL_CREDENTIAL_TYPE_SHARED_SECRET is the FALLBACK: the server generated a shared secret, returned EXACTLY ONCE in client_secret, and persisted only as a bcrypt hash. |
HealthFilter
HealthFilter classifies an agent by a derived runtime-health bucket. GAP-35 (#1084).
| Name | Number | Description |
|---|---|---|
| HEALTH_FILTER_UNSPECIFIED | 0 | HEALTH_FILTER_UNSPECIFIED disables the health filter (returns all health buckets). |
| HEALTH_FILTER_OVER_BUDGET | 1 | HEALTH_FILTER_OVER_BUDGET keeps agents whose daily token / cost usage is at or above the configured cap (treats unset cap = 0 as "no cap" and excludes the row from this bucket). |
| HEALTH_FILTER_UNDER_UTILIZED | 2 | HEALTH_FILTER_UNDER_UTILIZED keeps agents that have been provisioned for more than 7 days and have zero tokens consumed today — useful for spotting deployed-but-unused agents. |
| HEALTH_FILTER_RECENTLY_FAILING | 3 | HEALTH_FILTER_RECENTLY_FAILING keeps agents whose recent error rate crosses the GetAgentHealth "degraded"/"down" threshold. Implemented as a Redis-side post-filter on the page rather than a SQL predicate — agents that match are kept; non-matches are dropped from the returned page (page_size may collapse). The total_count still reflects the pre-filter SQL row count so the caller knows the upper bound. |
AgentService
AgentService manages the lifecycle, configuration, and health of AI agents within the UpsQuad platform. All operations are scoped to a tenant organisation via JWT-derived scope claims.
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| CreateAgent | CreateAgentRequest | CreateAgentResponse | CreateAgent provisions a new agent within a team, subject to the organisation's max_agents feature gate. |
| GetAgent | GetAgentRequest | GetAgentResponse | GetAgent retrieves a single agent by ID, excluding soft-deleted agents. |
| UpdateAgent | UpdateAgentRequest | UpdateAgentResponse | UpdateAgent modifies mutable agent fields (name, clearance, model_id). |
THE THREE FIELDS DO NOT CARRY THE SAME AUTHORITY, and that asymmetry is deliberate (#3569). name and clearance are gated on nothing beyond the caller's verified org — that is how this RPC has always behaved. model_id is gated on the L4-or-team-Manager matrix, the same one SetAgentPersona, SetAgentTools, SetAgentLLMEnablement and SetAgentGatewayRouting apply, because it chooses which upstream endpoint an agent's traffic reaches and every other write to a governed agent_configurations column carries that bar. Giving a governance column the weaker of two bars already present in one package would make this RPC the bypass for the other four. |
| DeleteAgent | DeleteAgentRequest | DeleteAgentResponse | DeleteAgent performs a soft delete by setting the deleted_at timestamp. Associated agent_configurations are NOT deleted. |
| ListAgents | ListAgentsRequest | ListAgentsResponse | ListAgents returns a paginated list of active agents, optionally filtered by pillar_id and/or team_id. |
| SetAgentPersona | SetAgentPersonaRequest | SetAgentPersonaResponse | SetAgentPersona updates the persona text in agent_configurations. Maximum 32000 characters. |
| SetAgentTools | SetAgentToolsRequest | SetAgentToolsResponse | SetAgentTools replaces the agent's per-agent MCP allowlist (the repurposed agent_configurations.tools JSONB — see the field docs). Narrow-only; gated on the L4-OR-team-Manager clearance matrix and a per-id tenant IDOR check (ADR-0022 W3/B8, #1744). |
| SetAgentLLMEnablement | SetAgentLLMEnablementRequest | SetAgentLLMEnablementResponse | SetAgentLLMEnablement replaces the agent's per-agent LLM enablement selection — the NARROW-ONLY set gate 7b of the Model Gateway enforces (MG-2.6; HLD #2649 section 5.1 step 7b; LLD #2801 section 11 task T4, issue #2810). It lands in agent_configurations.llm_enablement (migration 218) and it is the ONLY write path to that column.
Same shape and authorisation as SetAgentTools: an agent may DESELECT from what its team has been granted and may NEVER widen it. Every selected id is validated server-side against the team's approved set, resolved from the agent's OWN ROW, inside the request transaction. A request naming an id outside that set is refused with FailedPrecondition — a distinct refusal from the PermissionDenied a caller without authority gets. | | GetAgentLLMEnablement | GetAgentLLMEnablementRequest | GetAgentLLMEnablementResponse | GetAgentLLMEnablement returns the agent's current selection ALONGSIDE the ceiling it must stay inside, both derived by the same function the write validates against.
The ceiling is on the response deliberately. MG-2.6 requires that the UI never OFFER an over-grant — "an option that always errors is a bug report waiting to happen" — and the only way a picker can be structurally unable to offer one is to render the server's own ceiling rather than a set the client assembled from somewhere else. | | SetAgentGatewayRouting | SetAgentGatewayRoutingRequest | SetAgentGatewayRoutingResponse | SetAgentGatewayRouting is MG-4.1's per-agent opt-in: it routes this agent's LLM traffic through the Model Gateway (LLD #3138 task 4A, issue #3143, routing issue #3060). It is the ONLY write path to agent_configurations.gateway_routed (migration 240).
RUNTIME-AGNOSTIC. It applies to langgraph and claude_sdk alike and consults neither. That is the entire content of the requirement: the pre-existing endpoint knob (llm_base_url) is refused unless runtime == "claude_sdk", while the default runtime is langgraph, so the field that would carry "route this agent" could not be set on most of the fleet. This RPC does NOT touch llm_base_url, which stays an Anthropic-format claude_sdk override (#3060 constraint 5).
OPT-IN AND OPT-OUT ARE THE SAME ACTION — one RPC, one field, the other value. #3060 constraint 1: the staged cutover's whole value is that step 1 is undoable without a deploy, and a flag that can only be turned on is not an opt-in. There is deliberately no ClearAgentGatewayRouting.
Same authorisation as SetAgentTools and SetAgentLLMEnablement: the shared L4-OR-team-Manager matrix. THE FLAG IS NOT THE ORACLE — MG-4.2's migration baseline is computed from the ledger (llm_usage_events), never from this field (#3060 constraint 2). | | GetAgentHealth | GetAgentHealthRequest | GetAgentHealthResponse | GetAgentHealth reads real-time health signals from Redis and derives an overall health status (healthy, degraded, down, idle). | | RegisterAgent | RegisterAgentRequest | RegisterAgentResponse | RegisterAgent creates a headless (API-provisioned) agent with clearance, persona, model, tools, and optional autonomy level. | | SetAutonomyLevel | SetAutonomyLevelRequest | SetAutonomyLevelResponse | SetAutonomyLevel configures the agent's governance autonomy. | | SetBudgetCap | SetBudgetCapRequest | SetBudgetCapResponse | SetBudgetCap sets daily/monthly token and cost limits. | | GetBudgetStatus | GetBudgetStatusRequest | GetBudgetStatusResponse | GetBudgetStatus returns current-period usage vs caps. | | DeployAgentToTeam | DeployAgentToTeamRequest | DeployAgentToTeamResponse | DeployAgentToTeam assigns agent to team, wires governance, starts heartbeat. | | PauseAgent | PauseAgentRequest | PauseAgentResponse | PauseAgent transitions the agent to status=paused. Idempotent: calling on an already-paused agent returns the current row without re-emitting a lifecycle event. GAP-37 (#1085). | | ResumeAgent | ResumeAgentRequest | ResumeAgentResponse | ResumeAgent transitions the agent back to status=active. Idempotent. GAP-37 (#1085). | | ListAgentTemplates | ListAgentTemplatesRequest | ListAgentTemplatesResponse | ListAgentTemplates returns the prototype-template catalog backing step 1 of the redesigned 4-step Deploy wizard. Static-ish per release; no per-tenant authoring surface yet. GAP-38 (#1086). | | DeployFromTemplate | DeployFromTemplateRequest | DeployFromTemplateResponse | DeployFromTemplate is the one-shot wizard commit: equivalent to RegisterAgent + DeployAgentToTeam + SetBudgetCap + SetAutonomyLevel (+ guardrail attach) in a single transaction with rollback. The wizard never lands a half-deployed agent — every step succeeds or none of them do. GAP-41 (#1088). | | RegisterExternalAgent | RegisterExternalAgentRequest | RegisterExternalAgentResponse | RegisterExternalAgent registers an externally-hosted (BYO) agent as a first-class agent (runtime="external") AND issues its client credential in one transaction — #1864 trust-root PR-3 (#1956). The external agent later presents the credential to the platform token endpoint (PR-4) and receives the SAME agent-scoped gateway token an internal mint produces. Preferred credential is a customer-supplied public key (private_key_jwt); the fallback is a server-generated shared secret returned EXACTLY ONCE in the response. | | RotateExternalAgentCredential | RotateExternalAgentCredentialRequest | RotateExternalAgentCredentialResponse | RotateExternalAgentCredential rotates an external agent's public key or shared secret, keeping the client_id and the agent binding stable (rotated_at is stamped). A newly-generated secret is returned ONCE. #1956. | | RevokeExternalAgentCredential | RevokeExternalAgentCredentialRequest | RevokeExternalAgentCredentialResponse | RevokeExternalAgentCredential disables an external agent's credential (revoked_at is set). The agent is refused at its next token refresh (<=10 min) at the PR-4 endpoint. #1956. |
Scalar Value Types
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | double | float | Float | |
| float | float | float | float | float32 | float | float | Float | |
| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| bool | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |