Saltar al contenido
AI Workforce OS
ES EN
Crear cuenta

Runtime de agentes

El runtime de agentes es un servicio en Python (FastAPI y CrewAI) que convierte una tarea y su contexto en un resultado estructurado; solo habla con proveedores de modelo. Esta página es para quien opera o integra el runtime: su contrato HTTP, cómo elige proveedor, qué datos salen del proceso y cómo se controla el costo.

8 min de lectura

En esta página
  1. Rol y contrato HTTP
  2. Simulación y modo real
  3. Proveedores de modelo
  4. Orden de proveedores y respaldo
  5. Política de datos: ALLOWED_PROVIDERS
  6. Qué sale del proceso y redacción
  7. Herramientas: el modelo pide, el backend ejecuta
  8. Sandbox de código
  9. Contabilidad de costos y presupuestos
Volver arriba

Rol y contrato HTTP#

El backend llama al runtime por HTTP con una tarea, el perfil del agente y el contexto que armó. El runtime devuelve una salida estructurada validada y, si el agente quiere actuar, una lista de tool_requests. Nunca ejecuta una herramienta ni llama a un sistema externo: el modelo puede ser engañado por texto no confiable, pero solo puede proponer.

Cuando RUNTIME_TOKEN está definido, el runtime exige Authorization: Bearer <token> en todas las rutas salvo GET /healthz, comparado en tiempo constante; sin él responde 401. Los endpoints son:

Estado del runtime (sin token)Bash
curl http://localhost:8000/healthz
# {"status":"ok","mode":"live","provider":"anthropic",
#  "configured_providers":["anthropic","custom"],"default_provider":"anthropic"}
EndpointPropósito
POST /v1/plan, /v1/plan-phases, /v1/plan-phase, /v1/replanConvierten una solicitud o un proyecto en un plan de tareas para los agentes disponibles; replan reemplaza un nodo fallido
POST /v1/run-taskEjecuta una tarea para un agente; devuelve output, consults, tool_requests y usage
POST /v1/consult, /v1/review, /v1/synthesize, /v1/summarizeConsultas entre agentes, revisión de calidad, informe final y resúmenes
POST /v1/counterargumentContraargumento consultivo para una aprobación de alto riesgo
POST /v1/route, /v1/chat-replyEnrutamiento de chat y respuestas conversacionales
POST /v1/estimate, /v1/context-sizesRango de costo de un plan y tamaño del prompt; ninguno llama al modelo
POST /v1/provider-testUna llamada real pequeña para probar la clave de una organización; siempre responde 200 con un código estable
GET /healthzEstado, modo (simulation o live), proveedores configurados y proveedor por defecto; nunca claves

Atención

Define RUNTIME_TOKEN con el mismo valor en el backend y en el runtime. Sin él, cualquiera que alcance el puerto del runtime puede gastar tus claves de proveedor; el backend lo advierte en el log al arrancar.

Simulación y modo real#

El contrato HTTP es el mismo en ambos modos; solo cambia el motor.

  • Simulación: se activa con SIMULATION=true o cuando no hay ningún proveedor configurado. Devuelve planes, resultados, tokens y costos plausibles con latencia simulada (SIM_LATENCY_SCALE=0 elimina la espera), sin red ni costo, e ignora la política de proveedores porque ningún dato sale del proceso.
  • Real: el motor CrewAI llama al proveedor y valida la salida contra el esquema StructuredOutput. LLM_DIRECT_CALL=true cambia el bucle de CrewAI por una sola llamada de chat.
  • Degradado: si hay un proveedor configurado pero el motor real no arranca, el runtime responde en simulación y GET /healthz devuelve status: degraded con un warning.

Nota

Los campos opcionales provider y usage.failed_providers solo aparecen cuando contesta un proveedor real, así que el cliente no necesita distinguir el modo.

Consejo

SIMULATION=true siempre gana, incluso sobre la clave propia de una organización: no se hace ninguna llamada de red.

Proveedores de modelo#

Hay cinco proveedores. Uno queda configurado cuando su clave está en el entorno (para custom basta la URL base) o cuando la solicitud trae la clave propia de la organización. Un modelo custom se pasa como openai/<nombre>, y un modelo local sin costo puede tarificarse en 0 con CUSTOM_PRICE_IN_PER_M y CUSTOM_PRICE_OUT_PER_M.

Las organizaciones pueden traer su propia clave. Se sella con AES-256-GCM bajo una clave envuelta por CONNECTIONS_KEK, ninguna respuesta de la API la devuelve salvo sus últimos cuatro caracteres (key_last4) y solo viaja en claro por el salto interno del backend al runtime.

ProveedorConfiguraciónModelo por defecto
deepseekDEEPSEEK_API_KEY, DEEPSEEK_MODELdeepseek/deepseek-chat
anthropicANTHROPIC_API_KEY, ANTHROPIC_MODELanthropic/claude-sonnet-5-5
openaiOPENAI_API_KEY, OPENAI_MODEL, OPENAI_BASE_URL opcionalopenai/gpt-4.1
kimiKIMI_API_KEY, KIMI_MODEL, KIMI_BASE_URL opcionalopenai/kimi-k2-0905-preview (Moonshot, compatible con OpenAI)
customCUSTOM_LLM_BASE_URL (obligatoria), CUSTOM_LLM_API_KEY, CUSTOM_LLM_MODELEl de CUSTOM_LLM_MODEL: cualquier endpoint compatible con OpenAI, como vLLM, Ollama o una pasarela propia

Orden de proveedores y respaldo#

Para cada llamada el runtime arma una lista de intentos. Cada fuente agrega solo los proveedores que aún no están, así que todo proveedor configurado y permitido acaba siendo un respaldo:

  • MODEL_PROVIDER_ORDER_<ROL>: el role del agente en mayúsculas (por ejemplo MODEL_PROVIDER_ORDER_LEGAL) o el tipo de llamada: PLAN, REVIEW, CONSULT, SYNTHESIZE, SUMMARIZE, ROUTE, COUNTERARGUMENT.
  • role_providers de la política de modelos de la organización.
  • preferred_providers de la solicitud.
  • El orden global MODEL_PROVIDER_ORDER (por defecto deepseek,anthropic,openai,kimi,custom).
.envBash
# Runtime (agent-runtime)
SIMULATION=false                 # true: scripted engine, no network, no cost
DEEPSEEK_API_KEY=...             # one configured provider is enough
ANTHROPIC_API_KEY=...
CUSTOM_LLM_BASE_URL=https://llm.internal.example/v1   # OpenAI-compatible
MODEL_PROVIDER_ORDER=anthropic,deepseek,custom
MODEL_PROVIDER_ORDER_LEGAL=anthropic                  # per role / call kind
ALLOWED_PROVIDERS=anthropic,custom                    # operator ceiling
RUNTIME_TOKEN=<shared secret>    # same value in the backend

# Backend
RUNTIME_URL=http://agent-runtime:8000
RUNTIME_TOKEN=<shared secret>
BUDGET_USD=50                    # organization budget (default 50)
REQUEST_BUDGET_CAP_USD=5         # default cap per request
AGENT_BUDGET_USD=20              # default monthly cap per agent

Nota

Un fallo transitorio (timeout, límite de tasa, 5xx) se reintenta en el mismo proveedor PROVIDER_RETRIES veces (1 por defecto, con espera PROVIDER_RETRY_BACKOFF_S) antes de pasar al siguiente; PROVIDER_TIMEOUT_S (120 por defecto) acota cada intento. La política de la organización puede cambiar el modelo por rol (role_models), pero solo de un proveedor ya configurado y permitido.

Política de datos: ALLOWED_PROVIDERS#

La política de datos se aplica antes de enviar nada. La lista efectiva es ALLOWED_PROVIDERS del operador intersectado con allowed_providers de la solicitud: un proveedor fuera de la lista ni se llama ni se usa como respaldo, y una solicitud puede reducir el techo del operador pero nunca ampliarlo.

El backend completa allowed_providers desde la política de modelos de cada organización (GET/PUT /api/v1/settings/model-policy, solo owner y admin, y cada cambio queda auditado). Si no queda ningún proveedor permitido envía el valor centinela none, así que el runtime rechaza la llamada en lugar de ampliar el conjunto. Con byo_only, solo las claves propias de la organización pueden contestar. Los fallos son explícitos:

422 no_allowed_providerJSON
{
  "detail": {
    "code": "no_allowed_provider",
    "message": "No allowed model provider is configured for this request; nothing was sent.",
    "providers_tried": [],
    "allowed_providers": ["anthropic"],
    "configured_providers": ["deepseek"]
  }
}
CódigoHTTPSignificado
no_allowed_provider422Ningún proveedor configurado está permitido; no se envió nada
all_providers_failed502Fallaron todos los proveedores permitidos; providers_tried los lista
provider_no_balance402Las cuentas de proveedor probadas no tienen saldo; reintentar no sirve
call_budget_exceeded402La estimación de la llamada supera max_cost_usd, el margen que dio el backend; no se envió nada

Qué sale del proceso y redacción#

A un proveedor solo llegan los prompts de cada llamada. El contrato no tiene campos para credenciales, connection_id, URLs de API ni tokens, así que no pueden enviarse. Un prompt puede contener:

  • El texto de la solicitud del usuario y la persona, responsabilidades y herramientas del agente.
  • Salidas de tareas dependientes, memoria del agente y contenido externo.
  • Resultados de herramientas que el backend ya filtró y redactó.
  • Preguntas de consulta o de síntesis.

Nota

Los secretos se redactan dos veces. El backend redacta primero los resultados de herramientas; el módulo redact.py del runtime es la segunda barrera y cubre claves privadas, JWT, tokens de GitHub, AWS y Google, claves de API, tokens bearer, asignaciones tipo password, URLs con tokens y números de tarjeta válidos por Luhn. Se aplica a la salida del modelo y a los mensajes de error de los proveedores. El contenido externo, las salidas de dependencias y la memoria van solo en el prompt de usuario, dentro de bloques marcados como DATOS NO CONFIABLES con la instrucción de no obedecerlos; el system prompt se arma solo con lo que da el backend.

Herramientas: el modelo pide, el backend ejecuta#

Una solicitud de herramienta es una propuesta. El backend revisa la lista de herramientas del agente, los argumentos, el motor de políticas y el nivel de autonomía, y puede crear una aprobación para una persona. Solo entonces la ejecuta, con credenciales de la bóveda cifrada que nunca salen del backend. El resultado vuelve al modelo redactado y delimitado como datos no confiables.

Las capacidades marcadas como irreversibles en el manifiesto del proveedor, como email.send de Gmail, siempre requieren una decisión humana, sea cual sea la autonomía del agente. Una acción aprobada se reclama como máximo una vez, así que un reinicio no puede ejecutarla dos veces. Las herramientas sin conexión real usan adaptadores falsos en memoria, que es como funciona la simulación de punta a punta.

tool_requests en la respuesta de /v1/run-taskJSON
{
  "tool_requests": [
    { "tool": "email", "action": "send", "risk": "high",
      "args": { "to": "customer@example.com", "subject": "Proposal", "body": "..." } }
  ]
}

Sandbox de código#

La acción code.run permite que un agente pida ejecutar Python. Solo está disponible si el backend tiene SANDBOX_URL y SANDBOX_TOKEN; sin ellos se deniega con sandbox_unavailable. Por defecto cada ejecución es una aprobación que muestra el código, los archivos de entrada y el propósito, y queda ligada al hash del código y al SHA-256 de cada entrada: cambiar cualquiera exige una aprobación nueva.

Tras la aprobación el backend envía la ejecución a un runner aparte que inicia un contenedor desechable:

  • Sin red (--network none), raíz de solo lectura, usuario no root y todas las capabilities eliminadas.
  • Límites de tiempo, memoria, procesos y tamaño de salida (SANDBOX_MAX_RUNTIME_SECONDS, SANDBOX_MEMORY_MB, SANDBOX_PIDS, SANDBOX_MAX_OUTPUT_BYTES) y una lista de tipos de archivo de salida permitidos.
  • El entorno del runner no se reenvía, así que ningún secreto llega al código.
  • El kill switch, la pausa del agente o el modo de solo lectura abortan una ejecución en curso.

Nota

El modo escritorio es opcional y está apagado por defecto: permite que una organización deje correr sin aprobación el código que solo usa archivos del propio agente, con topes más bajos de código, entradas y ejecuciones por tarea.

Atención

El runner monta el socket de Docker, lo que equivale a root en ese host. Ejecútalo en una máquina dedicada y sin puertos publicados.

Contabilidad de costos y presupuestos#

Los precios viven en una sola tabla, model_prices.json, en USD por millón de tokens, idéntica byte a byte en el backend y en el runtime (una prueba en cada servicio lo exige). Un modelo ausente se cobra a la tarifa por defecto de la tabla y se avisa una vez en el log. PRICE_IN_PER_M y PRICE_OUT_PER_M fuerzan una tarifa para todos los modelos.

Antes de cada llamada el backend reserva la estimación máxima contra la solicitud, el agente y la organización. Después registra el mayor entre el costo que reporta el runtime y su propio recálculo, así que no depende de la exactitud del runtime. El gasto se lee de un ledger en Postgres y sobrevive a reinicios.

ControlPor defectoEfecto al excederse
BUDGET_USD (organización)50La llamada no se hace; no se reanuda subiendo un tope desde la API
REQUEST_BUDGET_CAP_USD (por solicitud; budget_cap_usd lo sustituye)Sin topeLa solicitud se pausa con budget.exceeded; sube el tope para continuar
AGENT_BUDGET_USD (mensual por agente)Sin topeLas llamadas del agente se pausan; sube el tope para continuar
COST_CONFIRM_THRESHOLD_USD1.0Una persona confirma el costo estimado antes de ejecutar
MAX_PARALLEL_PER_ORG8Las llamadas de la organización esperan en cola

Nota

La tabla de precios es una referencia para presupuestar, no lo que cobra tu propio contrato con el proveedor.