# Gestionar agentes de IA

Propón, configura, pausa y retira agentes de IA de canal desde tu aplicación: cada agente necesita la aprobación de una persona y tiene un tope de coste.

Tu aplicación puede proponer **agentes de IA de canal**: asistentes que responden cuando los mencionan y ejecutan tareas programadas, con el comportamiento de un agente publicado en AI Studio. Es lo más delicado que puede hacer una aplicación en UTILIA OS, porque un proceso automático decide con los permisos de qué persona trabaja el agente. Por eso tiene tres puertas y, además, cada agente necesita la aprobación de una persona antes de hacer nada.

## Requisitos

- El plan de tu organización incluye la capacidad «Agentes de canal» del Chat (que a su vez necesita AI Studio).
- Un Super Administrador ha asignado a tu aplicación el scope `chat:agents:manage:external`. No entra en ningún paquete de scopes, ni siquiera en `chat:full`: hay que elegirlo a propósito. Para solo consultar los agentes basta `chat:agents:view`.
- En cada canal, la concesión de tu aplicación incluye «Gestionar agentes de IA en nombre de tu equipo».

## Las puertas

**De la propuesta al agente trabajando**

1. **Scope de la aplicación** (Ficha de la aplicación): chat:agents:manage:external. Solo lo asigna un Super Administrador. Si no: 403 INSUFFICIENT_SCOPE.
2. **Concesión en el canal** (Panel del canal › Apps): Un propietario o administrador del canal la da, canal a canal. Si no: 404 sin concesión activa; 403 sin la capacidad de agentes.
3. **Aprobación de una persona** (Panel del canal): Un administrador del canal pulsa «Aprobar». Si no: el agente no trabaja y caduca a los 7 días.
4. **Permisos prestados** (En cada ejecución): Las herramientas del agente se recortan a lo que puede hacer hoy la persona de onBehalfOfUserId.

## El agente nace pendiente

Cuando tu aplicación propone un agente, la respuesta es `202 Accepted` y el agente queda en `PENDING_APPROVAL`: no responde a menciones ni ejecuta tareas hasta que un administrador del canal lo aprueba en UTILIA OS. Tu aplicación no puede aprobar sus propios agentes.

**Estados de un agente propuesto por una aplicación**

1. **Pendiente de aprobación** (`PENDING_APPROVAL`). Nace así. No responde ni ejecuta tareas. Caduca a los 7 días.
   - **Rechazado** (`REJECTED`, estado final). Se llega desde Pendiente de aprobación cuando: «Rechazar» o 7 días sin aprobación. No vuelve a trabajar.
2. **Activo** (`ACTIVE`). Se llega desde Pendiente de aprobación cuando: Un administrador del canal pulsa «Aprobar». Responde a menciones y ejecuta sus tareas.
3. **En pausa** (`PAUSED`). Se llega desde Activo cuando: Pausa manual, pause() o tope de coste superado. No trabaja.

Otras transiciones:

- En pausa → Activo: resume() o la reanudación desde el panel lo reactivan.
- Activo → Pendiente de aprobación: Cambiar sus herramientas o su acceso a la web con update() reabre la aprobación.
- En pausa → Pendiente de aprobación: Lo mismo si está en pausa.

_Solo trabaja en «Activo». Rechazado es definitivo: para volver a intentarlo, propón otro agente._

Para aprobar hace falta el permiso «Gestionar agentes creados por apps» (`CHAT_APP_AGENTS_MANAGE`) y ser propietario o administrador del canal. Si quien aprueba no es la persona cuyos permisos presta el agente, la interfaz le pide una confirmación adicional. Tu aplicación se entera del resultado por [webhook](#avisos).

## Con los permisos de quién: `onBehalfOfUserId`

Un agente no tiene permisos propios: trabaja con los de una persona real, la que indicas en `onBehalfOfUserId` al proponerlo. Esa persona tiene que estar activa, poder gestionar agentes (`CHAT_AGENTS_MANAGE`) y ser miembro del canal. En cada ejecución, UTILIA OS recorta las herramientas del agente a lo que esa persona puede hacer en ese momento: tu aplicación nunca amplía sus permisos.

Lo que estos endpoints no aceptan nunca: el modelo, el proveedor de IA, la temperatura ni claves de proveedores. El agente usa la configuración de IA por defecto de UTILIA OS. Tampoco puedes cambiar el agente de AI Studio (`aiAssetId`) de uno ya instalado: sería otro agente.

## Endpoints

Todos cuelgan de un canal con acceso: `/api/external/v1/chat/channels/:channelId/agents`. Además del scope, el plan tiene que incluir el Chat y la capacidad «Agentes de canal» (`chat.channel_agents`); si no, `403 MODULE_NOT_ENABLED` o `403 CAPABILITY_NOT_ENABLED`. Un canal sin concesión activa para tu aplicación responde `404 CHANNEL_NOT_FOUND`, y una concesión sin «Gestionar agentes de IA en nombre de tu equipo», `403 APP_NOT_AUTHORIZED_IN_CHANNEL`.

**`POST /api/external/v1/chat/channels/:channelId/agents`**

Propone un agente de IA en el canal. Scope chat:agents:manage:external. Responde 202 con el agente en PENDING_APPROVAL: no trabaja hasta que lo apruebe un administrador del canal. SDK: sdk.chat.agents.install(channelId, datos).

Parámetro de ruta: `channelId`.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `onBehalfOfUserId` | `string` | Sí | La persona cuyos permisos presta el agente: activa, con CHAT_AGENTS_MANAGE y miembro del canal. |
| `aiAssetId` | `string` | Sí | Agente publicado de AI Studio al que esa persona tiene acceso. No se puede cambiar después. |
| `displayName` | `string` | Sí | Nombre visible en el canal, hasta 200 caracteres. |
| `iconOverride` | `string` | No | Icono, hasta 64 caracteres. |
| `colorOverride` | `string` | No | Color, hasta 32 caracteres. |
| `allowedTools` | `string[]` | No | Herramientas que puede usar. Siempre se recortan a lo que puede hacer la persona de onBehalfOfUserId. |
| `respondsToMentions` | `boolean` | No | Si responde cuando lo mencionan en el canal. |
| `maxDailyMessages` | `number` | No | Mensajes que puede publicar al día, de 1 a 100. |
| `webAccessMode` | `enum` | No | NONE (sin búsqueda web), ANY (cualquier fuente pública, sin lista de dominios) o ALLOWLIST (solo los dominios de webAllowedDomains). |
| `webAllowedDomains` | `string[]` | No | Dominios permitidos con ALLOWLIST. |
| `channelContextEnabled` | `boolean` | No | Si el agente lee el contexto del canal. |

**`GET /api/external/v1/chat/channels/:channelId/agents`**

Agentes que ESTA aplicación ha creado en el canal, con su estado. Scope chat:agents:view. SDK: sdk.chat.agents.list(channelId).

Parámetro de ruta: `channelId`.

**`PATCH /api/external/v1/chat/channels/:channelId/agents/:agentId`**

Configura un agente propio. Scope chat:agents:manage:external. Mismos campos que el alta, todos opcionales, salvo onBehalfOfUserId y aiAssetId. Cambiar allowedTools o el acceso a la web lo devuelve a PENDING_APPROVAL. SDK: update(channelId, agentId, datos).

Parámetros de ruta: `channelId`, `agentId`.

**`POST /api/external/v1/chat/channels/:channelId/agents/:agentId/pause`**

Pausa un agente activo. Scope chat:agents:manage:external. Responde 201 con el agente. SDK: pause(channelId, agentId).

Parámetros de ruta: `channelId`, `agentId`.

**`POST /api/external/v1/chat/channels/:channelId/agents/:agentId/resume`**

Reanuda un agente en pausa. Scope chat:agents:manage:external. Responde 201 con el agente. SDK: resume(channelId, agentId).

Parámetros de ruta: `channelId`, `agentId`.

**`DELETE /api/external/v1/chat/channels/:channelId/agents/:agentId`**

Retira un agente propio del canal. Scope chat:agents:manage:external. Si ya publicó mensajes, se conserva en pausa como registro histórico (retainedAsHistory). SDK: remove(channelId, agentId).

Parámetros de ruta: `channelId`, `agentId`.

Si al configurarlo cambias sus herramientas o su acceso a la web, el agente vuelve a `PENDING_APPROVAL` y deja de trabajar hasta una nueva aprobación. Los cambios de nombre, icono o color no la necesitan.

## Ejemplo

### JavaScript / TypeScript

```typescript
import { UtiliaSDK } from '@utilia-os/sdk-js';

const sdk = new UtiliaSDK({
  baseURL: 'https://os.utilia.ai/api',
  apiKey: process.env.UTILIA_API_KEY, // utl_xxx
});

// 1) Solicitar el alta. onBehalfOfUserId es OBLIGATORIO: es la persona cuyos
//    permisos prestará el agente. Responde 202 y el agente nace PENDING_APPROVAL.
const agente = await sdk.chat.agents.install('cnv_8f3a21', {
  onBehalfOfUserId: 'usr_7a1b',
  aiAssetId: 'ast_vigia',
  displayName: 'Vigía de incidencias',
  allowedTools: ['crm_clients', 'crm_opportunities'],
  respondsToMentions: true,
  maxDailyMessages: 20,
});

console.log(agente.status); // 'PENDING_APPROVAL' -> aún no ejecuta

// 2) Listar los agentes que ESTA app ha creado en el canal, con su estado.
const agentes = await sdk.chat.agents.list('cnv_8f3a21');
for (const a of agentes) {
  console.log(a.displayName, a.status);
}

// 3) Configurar. Ampliar allowedTools devuelve el agente a PENDING_APPROVAL.
await sdk.chat.agents.update('cnv_8f3a21', agente.id, {
  displayName: 'Vigía de incidencias (pro)',
});

// 4) Pausar y reanudar (solo desde ACTIVE / PAUSED respectivamente).
await sdk.chat.agents.pause('cnv_8f3a21', agente.id);
await sdk.chat.agents.resume('cnv_8f3a21', agente.id);

// 5) Retirar del canal. Si publicó mensajes, se conserva como histórico.
const resultado = await sdk.chat.agents.remove('cnv_8f3a21', agente.id);
console.log(resultado.removed, resultado.retainedAsHistory);
```

### Python

```python
import os
from utilia_sdk import (
    InstallChatAgentInput,
    UpdateChatAgentInput,
    UtiliaSDKSync,
)

sdk = UtiliaSDKSync(
    base_url="https://os.utilia.ai/api",
    api_key=os.environ["UTILIA_API_KEY"],  # utl_xxx
)

# 1) Solicitar el alta. on_behalf_of_user_id es OBLIGATORIO: es la persona cuyos
#    permisos prestará el agente. Responde 202 y el agente nace PENDING_APPROVAL.
agente = sdk.chat.agents.install(
    "cnv_8f3a21",
    InstallChatAgentInput(
        on_behalf_of_user_id="usr_7a1b",
        ai_asset_id="ast_vigia",
        display_name="Vigía de incidencias",
        allowed_tools=["crm_clients", "crm_opportunities"],
        responds_to_mentions=True,
        max_daily_messages=20,
    ),
)

print(agente.status)  # 'PENDING_APPROVAL' -> aún no ejecuta

# 2) Listar los agentes que ESTA app ha creado en el canal, con su estado.
for a in sdk.chat.agents.list("cnv_8f3a21"):
    print(a.display_name, a.status)

# 3) Configurar. Ampliar allowed_tools devuelve el agente a PENDING_APPROVAL.
sdk.chat.agents.update(
    "cnv_8f3a21",
    agente.id,
    UpdateChatAgentInput(display_name="Vigía de incidencias (pro)"),
)

# 4) Pausar y reanudar (solo desde ACTIVE / PAUSED respectivamente).
sdk.chat.agents.pause("cnv_8f3a21", agente.id)
sdk.chat.agents.resume("cnv_8f3a21", agente.id)

# 5) Retirar del canal. Si publicó mensajes, se conserva como histórico.
resultado = sdk.chat.agents.remove("cnv_8f3a21", agente.id)
print(resultado.removed, resultado.retained_as_history)
```

Al retirar un agente que ya había publicado mensajes, no se borra: se conserva como histórico (`retainedAsHistory` en la respuesta).

## Tope de coste de IA

Quien puede editar la aplicación fija un tope mensual de coste de IA para los agentes de tu aplicación, en la pestaña «Chat» de la configuración de la aplicación. Si en un mes los agentes lo superan, se pausan solos, llega el aviso `CHAT_AGENT_BUDGET_EXCEEDED` y no vuelven a responder hasta que se amplíe el tope o empiece el mes siguiente. Este tope se suma a la cuota de IA de tu organización.

Cuantas menos herramientas y menos mensajes diarios tenga un agente, más barato y más fácil de aprobar será.

## Avisos

Estos avisos llegan solo a la aplicación que creó el agente, si tiene el scope `chat:events:subscribe` y los ha declarado en su webhook, aunque ya no tenga acceso al canal:

| Evento | Cuándo llega |
| - | - |
| `CHAT_AGENT_PENDING_APPROVAL` | Propusiste un agente (o cambiaste sus herramientas o su acceso a la web) y espera aprobación. |
| `CHAT_AGENT_APPROVED` | Un administrador lo aprobó: pasa a `ACTIVE` y empieza a trabajar. |
| `CHAT_AGENT_REJECTED` | Se rechazó o caducó a los 7 días sin aprobación. |
| `CHAT_AGENT_BUDGET_EXCEEDED` | Se superó el tope mensual de coste y el agente se pausa. |

Declara los eventos con estos nombres en mayúsculas; los nombres internos con puntos no valen. Cómo recibirlos y verificarlos está en [Webhooks de chat](https://os.utilia.ai/docs/developers/chat-platform/webhooks).

## Siguiente paso

Cómo se ve la aprobación desde el lado del equipo, y cómo se añade un agente desde el propio Chat, está en [Añadir un agente de IA a un canal](https://os.utilia.ai/docs/guides/chat/ai-agents).
