El spec del agent¶
El tipo que más peso soporta de toda la plataforma: el Builder lo edita, la base de datos lo versiona, la factory lo instancia y los clientes lo exportan como YAML a sus propios repositorios de git.
Se genera a partir del código fuente, porque el razonamiento vive en los docstrings — y por eso la documentación de campos generada más abajo se queda en inglés.
app.agents.spec.AgentSpec
¶
Bases: BaseModel
Everything that defines an agent's behaviour.
Deliberately excluded: anything about where the agent runs (surfaces, channels) and anything about who may use it (owner, sharing). Those are deployment and access facts; keeping them out means the same spec can be exported, reviewed and reused across organizations.
trace_content
property
¶
How much of a run this agent's spans may carry.
No block at all means full, which is what an agent published before the
mode existed asks for. Read here rather than at each call site because
every specialist this agent builds inherits it, and a caller that forgets
the None case silently hands the deployment's global instrumentation a
specialist with content on.
to_yaml
¶
Render the spec for a client's git repository.
Keys keep spec order rather than being sorted, so a diff reflects what changed rather than where it happens to sort. UUIDs become strings so the file round-trips through any YAML reader.
from_yaml
classmethod
¶
Parse a spec written or edited by hand.
A spec_version newer than this deployment understands is refused here
rather than accepted because its fields happen to parse: a document from
a later deployment can carry constructs a >-guarded field cannot see
(a binding kind, a capability id) that this code would misread as an
older shape. Only imported text is checked - a stored spec is loaded
through model_validate and is never newer than the code that wrote it,
so this refusal cannot make an existing row unreadable.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the document is not a mapping, or targets a spec
version newer than |
Variables¶
{{nombre}} en instructions se rellena al empezar cada run: con una variable del
sistema que la plataforma conoce (la fecha y la hora, quién ha iniciado sesión y sus
grupos, la organización, el agent, el canal) o con una de las variables propias del agent.
time_zone decide qué reloj lee {{current_time}}. Publicar rechaza un nombre que
no sea ninguna de las dos cosas.
app.agents.spec.PromptVariableSpec
¶
Bases: BaseModel
A custom variable: a name the instructions may write as {{name}}, and its text.
Written once and used in as many places as it needs, so the value that changes
- a support address, a policy version, a product name - changes in one place.
The name cannot be a system variable's (app/agents/prompt_variables.py),
which publish checks.
Delegación¶
Dos formas, y Conceptos explica a
cuál recurrir. subagents contiene la primera; la segunda vive en la
configuración de la capability subagents.
app.agents.spec.SubagentRef
¶
Bases: BaseModel
One published agent this agent may delegate to, pinned to a version.
Two ids rather than one, and the second is the whole point. A delegate is a real agent - versioned, permission-checked at publish, with its own capabilities, its own model and its own collections - and a reference that named only the agent would let its behaviour change under a published parent with nothing recording that anything had changed. Pinning means a fix to a delegate reaches its callers when somebody says so, which is the same guarantee publishing gives everywhere else in this product.
The cost of pinning is real and is paid in the Builder: a parent whose delegate has moved on is stale, and staleness that nothing surfaces is a bug frozen in place forever. The draft compares each pin against the delegate's current version and offers to move it.
A pin whose version no longer exists fails the run, loudly, naming the delegate. Never a quiet fall back to the current version: the reason to pin is that nothing changes without a decision, and a silent upgrade is worse than a refusal because nobody finds out.
external_agents nombra agents remotos de A2A registrados en la organización, a los que la
delegación llama junto a estos.
app.agents.spec.ExternalAgentRef
¶
Bases: BaseModel
A remote agent this agent may delegate to over A2A (#2092).
Another vendor's agent, registered in the organization by the URL of its agent card. Nothing about it is versioned or pinned here: its owner changes it on their side, and the reference names the registration, so each run reads that registration's current endpoint and credential - a refreshed card that moves the endpoint applies from the next run, and a removed registration refuses the run. Called by the same delegation tool as a published delegate, under the same budget, and addressed by its registration's handle.
app.agents.spec.SpecialistSpec
¶
Bases: BaseModel
A specialist defined inside another agent, rather than published.
Worth having, and worth being honest about. A "summarise this in three bullets" specialist should not require somebody to publish an agent, and this is that: a name, a description the parent's model reads before delegating, instructions, and - because a summariser that cannot read the collection is useless - its own capabilities, collections and skills.
Which makes it an agent in every way except one, and the exception is the
important one: a specialist is not versioned. It has no version row, it
cannot be pinned, nothing else can reference it, and editing the parent
changes it. That is the difference between this and :class:SubagentRef, and
it is why the Builder must present them as two different things rather than
two tabs of one.
The risk this shape exists to contain is a second, parallel notion of
"agent" - one that publish validation does not walk and the permission model
cannot see. It is contained by refusing to write a second format: this is a
typed subset of :class:AgentSpec, using the same
:class:CapabilityBindingSpec, validated by the same recursive pass in
validate_spec, and assembled by the same build_agent. One spec type, one
validator, one builder, one Builder component, each used recursively. If any
of those five grows a second copy for specialists, the copy is the bug.
Deliberately absent, each because it only means something for a thing with a version or an owner:
budget- inside a delegation the parent's caps bind. Two budget guards metering one shared ledger would double-count every request.notificationsandobservability- a specialist is not the subject of an alert or a Logfire service; the run it happens inside is.mcp_servers- an MCP connection is organization-scoped configuration, and reaching one through a specialist nobody published is the wrong door. There is deliberately no route to one from here:share_with_delegateslends capability bindings, and an MCP connection is not a capability, so naming one there would configure nothing. A specialist that needs an external tool is a specialist that should be a published agent.subagents- a specialist does not delegate further. Nesting is whatmax_depthbounds, and it is bounded for published delegates, which are reviewable.
to_agent_spec
¶
This specialist as the spec the factory already knows how to build.
The one method that keeps "one spec type, one validator, one builder" true
rather than aspirational. A specialist is a subset of an agent, so the way
to build one is to say which agent it is and hand it to build_agent -
not to write a second assembly path that will drift from the first the
moment a field is added to either.
fallback_model_profile_id is the parent's, used when the specialist
names none: a specialist with no model of its own runs on the model of the
agent that called it, which is both the least surprising answer and the
only one that works when the parent is the only agent whose profile the
author chose.
The fields this drops are the ones :class:SpecialistSpec deliberately
does not have - budget, notifications, observability, MCP connections,
subagents - so they arrive at their AgentSpec defaults: no cap of its
own (the run's caps bind), no alerts of its own, no Logfire project of its
own, no connections, and no delegating further.
trace_content is the one part of the parent's observability block that
has to come with it, and the caller passes the parent's. A project is a
destination and a specialist has none of its own; content is a rule
about what may be recorded anywhere, and the run it is recorded in
belongs to the parent. Dropping it left an agent published with
content="none" exporting its specialist's prompts, outputs and tool
arguments to the deployment's own project through the global
instrumentation - the guarantee held for the agent and not for the run
(#1699). full is the default, so a caller with no parent to speak for -
promoting a specialist into a draft agent - converts as it always did.
Budgets¶
app.agents.spec.BudgetSpec
¶
Bases: BaseModel
Spending limit for this agent.
One monthly cap, metered against this agent's own runs. The platform has exactly two budget levels - the agent's and the organization's - and this is the agent's half.
Avisos¶
app.agents.spec.NotificationSpec
¶
Bases: BaseModel
Which of this agent's alerts are sent, and who hears each one.
Per agent because the alerts are about an agent. A deployment-wide switch made the noisy agent and the one nobody may miss the same setting, so the only way to quieten the first was to go deaf to the second.
What is not here: the organization's own monthly cap. That limit is not this agent's to describe - it stops every agent in the organization, its ceiling is set in the organization's settings, and an agent's author cannot raise it. Its alert goes to the organization's admins and is not configurable from a spec.
app.agents.spec.AlertSpec
¶
Bases: BaseModel
Whether one kind of alert is sent for this agent, and to whom.
Every audience is resolved and the addresses are merged, so naming the same
person twice mails them once. Each recipient's own
/settings/notifications switch is applied last and can only ever remove
them: an agent cannot conscript somebody into an inbox they opted out of.
app.agents.spec.AlertAudience
¶
Bases: StrEnum
Who an agent's alert reaches.
A list of these rather than one value, because the real answers are unions: "the person who asked, and the admins" is the sensible default for an approval and cannot be said with a single choice.
Deliberately roles rather than addresses, with CHOSEN as the one escape
hatch. An audience of user ids goes stale the moment somebody leaves, and a
spec is exported to a client's repository - ADMINS still means the right
people after a reorganisation, and it means them in whichever organization
the spec is imported into.
ADMINS
class-attribute
instance-attribute
¶
The organization's owners and admins, plus the deployment's app admins.
OWNER
class-attribute
instance-attribute
¶
Whoever owns the agent - the person who would fix its configuration.
INITIATOR
class-attribute
instance-attribute
¶
Whoever started the run. Nobody, for a run no person began.
CHOSEN
class-attribute
instance-attribute
¶
Exactly the members named in user_ids.
Ajustes del modelo¶
app.agents.spec.ModelSettingsSpec
¶
Bases: BaseModel
How this agent asks its model to behave.
A deliberately small window onto Pydantic AI's ModelSettings. The full
set includes escape hatches for someone debugging a provider - raw bodies,
raw headers, token biases - which in a Builder are an invitation to paste
something that breaks a published agent, and knobs that only some providers
implement, which quietly mean something else after a model swap.
_MODEL_SETTINGS_WITHDRAWN says why each excluded key went. What is left
is what an agent author reaches for: how varied the answer is, how long it
may be, how long it may take, and whether tools may run at once.
Reasoning is not here. It is the thinking capability, and a second
control writing the same provider parameter would disagree with it silently.
Every field is optional and unset means unset, which is the one property
the rest of this model is arranged around. None is not "send the
provider's default" - it is "do not send this parameter", and the difference
is a run that fails: reasoning models reject temperature outright, so an
agent that never chose one must produce a request with no temperature key
at all. Hence the serializer: an unset field is absent from the stored
spec rather than stored as null, so nothing downstream - the merge in
app/agents/factory.py, a YAML export, the Builder deciding whether to
show a field as touched - has to know that a null here means "no".
Observabilidad¶
app.agents.spec.ObservabilitySpec
¶
Bases: BaseModel
Where this agent's traces go.
Per agent rather than per deployment because the interesting case is a client's agent whose traces belong in the client's own Logfire project, not in the operator's. The deployment-wide configuration stays as it is and keeps receiving everything else; this only redirects the runs of the agent that asks for it.
The token is a reference, never a value - like every other credential a spec
names. A spec is exported as YAML into somebody's repository, and a write
token in a checked-in file is a token that has to be rotated. The reference
is checked at publish for existence, tenant and the logfire purpose the
same way _check_logfire_secret gates the environment path: an unusable or
wrong-service token there runs the agent untraced, which is far too late to
learn it was never reachable.
organization and project are the other half of that redirection, and they
are here rather than in deployment settings for the same reason the token is:
a token is a write credential and names neither, so a deployment knows
where it sends an agent's traces and not where to read them. Both are slugs a
client tells us; nothing can derive them. With neither set the run's trace id
is still recorded and no link is offered - which is a configuration fact
rather than a promise the schema is failing to keep (#206).
Voz a texto¶
El proveedor, el modelo, la clave y el idioma con los que se transcriben el dictado de un agent y las notas de voz de canal que hereda, cuando difieren de los de la organización. La clave es el id de un secreto del vault, nunca la clave misma. Servicios de ML tiene el orden en que se resuelven. Un spec que lleva este bloque no se puede importar en un despliegue anterior al bloque: su spec rechaza los campos que no conoce.
app.agents.spec.SpeechToTextSpec
¶
Bases: BaseModel
Which engine turns speech into text for this agent, where it differs.
An override on the organization's speech-to-text settings, read wherever a
recording reaches this agent: dictation in the /chat composer, and voice
notes sent to a channel bot whose own pair is left to inherit. Unset fields
inherit - the pair from the organization, the language from the
organization and then the engine's own detection - so an agent can pin
language: pl and keep the organization's provider.
The provider and model are a pair, both or neither. A provider with no model has nothing to call and a model with no provider has nowhere to go. The rule is enforced at publish rather than here, together with the catalog and language checks: the Builder autosaves a draft on every change, and a validator on this model would refuse the draft between picking a provider and picking its model.
The key travels with the pair. secret_id names the vault key the
pair is sent with - an api_key whose purpose is the provider, which the
publisher must be able to view, since binding a key lends it to everyone
who can run the agent. A pair without a key is refused at publish; a key
without a pair would be a key for nothing. An agent that overrides only the
language names neither, and keeps the organization's pair and its key.
The spec carries the id, never the key, so it stays safe to export.
Voz¶
Cómo atiende el agent una llamada de voz: el modelo realtime, la
clave del vault, la voz y los límites propios de la llamada. Es opcional, y un
bloque con todo vacío se lee como si no hubiera bloque, así que los specs
escritos antes de él cargan sin cambios y SPEC_VERSION no se movió. Como el
bloque de voz a texto, un spec que lo lleva no se puede importar en un
despliegue anterior al bloque.
| Campo | Qué contiene |
|---|---|
provider |
Un provider del catálogo realtime: openai, azure, google o xai |
model |
Un modelo realtime de ese provider. Para Azure, déjalo vacío y usa los dos campos de abajo |
deployment |
Solo Azure: el nombre del deployment al que van las llamadas |
base_model |
Solo Azure: el modelo del catálogo que ejecuta ese deployment, que decide sus voces y su precio |
secret_id |
La clave del vault con la que se sostienen las llamadas: un id, nunca la clave |
voice |
Una de las voces del modelo; vacío usa la voz por defecto del modelo |
language |
Un código ISO-639-1 en el que habla el agent; vacío sigue a quien llama |
max_call_seconds |
La llamada más larga, de 60 a 3000; vacío son 600 (10 minutos) |
voice_note |
Hasta 1000 caracteres que se añaden a las instrucciones del agent solo en las llamadas |
Toda regla que abarca varios campos se comprueba al publicar la versión, no al guardar el borrador, porque el Builder autoguarda entre elegir un provider y elegir su modelo. La publicación se rechaza, nombrando la sección de voz, cuando:
- falta el provider, o el catálogo de este despliegue no tiene ese provider;
- falta el modelo o no es un modelo realtime que el catálogo ofrezca para ese provider (para Azure, el modelo base), o una sección de Azure no nombra ningún deployment;
- la voz no es una de las voces del modelo;
- no se ha elegido ninguna clave, o la clave no pasa la comprobación común: su
propósito no es el provider, su tipo no es el del provider (
api_key, oazure_openaipara Azure), pertenece a otra organización o quien publica no puede verla; - el idioma no es un código que las llamadas acepten.
Las capabilities que una llamada no puede ejecutar no bloquean la publicación; se dejan fuera de las llamadas y el Builder las enumera.
app.agents.spec.VoiceSpec
¶
Bases: BaseModel
How this agent holds a spoken conversation (#54).
A pick from the realtime catalog and the vault key it is held with - the
same A+ shape as speech to text. For Azure, deployment is the deployment
name (free text) and base_model the catalog model it runs, which decides
the voices and the price; the endpoint and API version come from the
azure_openai secret.
Every rule that spans fields is checked at publish, not here, for the
reason SpeechToTextSpec gives: the Builder autosaves a draft between
picking a provider and picking its model.
Capabilities a realtime session cannot carry do not block publishing; a
call hands them to the agent's text model (app/agents/hand_off.py).