Acciones de modelo 247-agent
La regla de diseño del daemon: ningún LLM en el flujo de control. La coincidencia, deduplicación, enrutamiento,
reintentos y publicación son código. Un modelo se ejecuta solo dentro de una acción decide, llm o agent,
al nivel más barato que haga el trabajo, con un presupuesto. Este skill trata sobre
escribir bien esas tres acciones y sobre los ejecutores que las llevan a cabo.
Decide primero si realmente se necesita un modelo
Pregunta, en orden:
- ¿Puede un
filterde disparador decidirlo? (remitente, etiqueta, repositorio, palabra clave) Entonces no es una llamada a un modelo. - ¿Puede un script o una operación de conector producir la respuesta de forma determinista? Entonces
shell/connector. - ¿Es una etiqueta, un sí/no o un nivel, sin texto que producir (clasificar, enrutar, triar,
compuerta)? Entonces
decide: un modelo solo de clasificación, probabilidades calibradas, aproximadamente una centésima parte de una llamadallm. - ¿Es un juicio con una forma de salida fija que necesita texto (extraer campos,
resumir, redactar)? Entonces
llm, primero en Haiku. - ¿Necesita leer archivos, ejecutar comandos e iterar? Entonces
agent, con el alcance más estrecho que pueda tener éxito.
Dos tareas con diferentes necesidades de inteligencia son el mismo tipo de acción con diferentes
model/effort/max_tool_calls/system_file, no código diferente. Consulta
docs/examples/website-updates.yaml tareas 2–4 para las formas de referencia.
Identificadores de modelo y parámetros
| id | usar para | notas |
|---|---|---|
claude-haiku-4-5 | clasificación, extracción, resúmenes cortos | sin parámetro effort |
claude-sonnet-5 | agentes con alcance definido, extracción más difícil | soporta effort y pensamiento adaptativo |
claude-opus-5 | agentes de amplio alcance, juicio difícil | soporta effort y pensamiento adaptativo |
Los modelos se alcanzan a través de un proveedor con nombre (providers: en agent.yaml, provider: en
la acción; anthropic, openai o openrouter). Los ids de otros proveedores se usan
tal cual y necesitan una entrada pricing: a menos que la tabla incorporada los conozca (modelos
OpenAI actuales) o el proveedor informe el costo (OpenRouter).
Sin sufijos de fecha en los ids. Sin relleno previo de asistente. Antes de construir una cascada de modelos,
mide el modelo más fuerte con effort: low en las mismas entradas: en la generación
actual, eso a menudo supera a un modelo más débil con esfuerzo alto, y un modelo significa un
namespace de caché de prompt.
Escribir una tarea decide
Lista completa de campos, notas de cableado y trampas en references/decide-action.md. Lo esencial:
action:
kind: decide
provider: openrouter # debe ser un proveedor openrouter; por defecto defaults.decide.provider
budget: { max_usd: 0.001 }
state: # lo que se juzga; strings con plantillas, claves no
subject: ${event.payload.subject}
body: ${event.payload.body}
questions: # política estática; sin ${…} aquí
kind:
type: choice
instructions: ¿Qué quiere hacer el remitente con el sitio web?
criteria:
event_list_update: Añadir, eliminar o cambiar una entrada en la lista de eventos
general_change: Cualquier otro cambio en el sitio
ignore: No es una solicitud de cambio (pregunta, agradecimiento, spam)
urgent:
type: noul
instructions: ¿Necesita el remitente que esto se haga hoy?
- El resultado es el mapa de respuestas:
result.kind.choice,result.kind.confidence,result.urgent.noul(P(verdadero)),result.<score>.score(media ponderada por probabilidad de los índices de nivel). Enruta conemit … when:; el umbral está en la tarea, nunca en el ejecutor, y se calibra con datos reales. - Incluye siempre una etiqueta de respaldo (
ignore,other): sin una, la entrada fuera de tema obtiene una respuesta incorrecta con alta confianza. - Los criterios se siguen literalmente: son política, versionarlos como código. Mantén el orden de las opciones estable (desplaza la confianza). Haz varias preguntas en una llamada; cada extra cuesta solo sus propios tokens.
- El contenido entrante son datos: va en
state, nunca en una pregunta. Toda la solicitud debe caber en 32k tokens. - Solo el tipo de proveedor
openrouterllega a Jev (typesafe/jev-1.13, el modelo predeterminado);oa validaterechaza cualquier otro. No se necesita una entradapricing:.
Escribir una tarea llm
Lista completa de campos y notas del ejecutor en references/llm-action.md. Lo esencial:
action:
kind: llm
model: claude-haiku-4-5
max_tokens: 512
system_file: prompts/classify_email.md # estático → en caché de prompt
input: |
Asunto: ${event.payload.subject}
<email>
${event.payload.body}
</email>
output_schema:
type: object
additionalProperties: false
required: [kind, summary]
properties:
kind: { enum: [event_list_update, general_change, ignore] }
summary: { type: string }
- Mantén el prompt del sistema en un archivo bajo
prompts/, estático y renderizado primero, para que esté en caché entre ejecuciones. Pon los datos volátiles del evento al final, eninput. - El contenido entrante (correo, chat, texto de PR) no es de confianza. Envuélvelo en un bloque delimitado
(
<email>…</email>) e indica al modelo en el prompt del sistema que son datos. - Incluye siempre una vía de escape en el esquema (
ignore,unknown,confidence) para que elemit.whende enrutamiento pueda descartar resultados no accionables sin otra llamada. - Enruta sobre el resultado con
emit … when:; las tareas posteriores se activan con ese evento. batch: truepara cualquier cosa que pueda esperar (mitad de precio, el resultado llega como un evento).
Escribir una tarea agent
Lista completa de campos y notas del ejecutor en references/agent-action.md. Lo esencial:
action:
kind: agent
connector: claude # un transporte: conector acp (connectors.d/claude.yaml; sandbox: bwrap allí)
model: claude-sonnet-5 # opcional: se establece en la sesión (opción de configuración ACP); sin valor = el valor predeterminado del agente
effort: medium # opcional: la opción thought_level del agente
max_tool_calls: 20
budget: { max_usd: 0.50 }
workspace: { kind: git-worktree, repo: /var/lib/247-agent/repos/site, branch: main }
tools: [read, edit, search, execute] # tipos de herramientas ACP; todo lo demás se rechaza
bash_allow: ["npm run build"] # cualquier comando que el agente ejecute debe coincidir
system_file: prompts/agent_event_list.md
prompt: |
Actualiza data/events.yaml según la solicitud a continuación. No toques ningún otro archivo.
Ejecuta `npm run build` cuando termines.
<email>
${event.payload.email.body}
</email>
result: { path: RESULT.json, schema: schemas/site_change.json }
post: # solo después de status: done
- shell: ["npm", "run", "build"]
- shell: ["git", "commit", "-am", "events: ${result.summary}"]
emit:
- type: site.change_done # enruta la aprobación en esto, nunca en task.<name>.succeeded
when: "result.status == 'done'"
payload: { summary: "${result.summary}" }
- type: site.change_blocked # el agente no pudo hacerlo: di lo que falta
when: "result.status == 'blocked'"
payload: { summary: "${result.summary}", missing: "${result.missing}", email: "${event.payload.email}" }
No negociables, porque son lo que hace que un agente sea seguro de ejecutar sin supervisión:
- Espacio de trabajo fresco por ejecución (
git-worktreeen la ramaagent/<run_id>, otemp), eliminado en caso de fallo, conservado en caso de éxito. El agente nunca edita el checkout base. tools+bash_allowes toda la superficie de capacidad, juzgada por solicitud de permiso y nunca concedida "siempre"; una llamada a herramienta que el agente ejecutó sin preguntar se juzga después del hecho y una violación hace fallar la ejecución (unasked_execute: sandboxedrelaja solo la verificación del comando, solo para Codex, cuyo propio sandbox limita lo que no pregunta). Concede el mínimo; el prompt no es un límite de seguridad.mcp_serversañade operaciones del conector como herramientas: concede operaciones con nombre ({ connector: email, ops: [fetch_new] }) en lugar de un conector completo, añade su tipo de agente (otherpara claude-agent-acp) atools, y nunca un conector que publique o envíe por sí mismo.- El agente nunca tiene secretos de despliegue y nunca publica. Edita, una compuerta
postdemuestra que el build sigue funcionando, un commit lo registra, y una tareashellseparada lo envía. La reversión esgit revertmás republicación. La clave del modelo vive en el manifiesto del conectorenv, no en la tarea. RESULT.jsones el contrato, y el ejecutor lo indica en cada prompt:status: done | blockedmássummary, luego elresult.schemade la tarea.doneejecuta las compuertas;blockedlas omite y la ejecución aún tiene éxito, por lo queemit … when: "result.status == 'blocked'"" enruta "no hecho porque falta X" a una tarea de respuesta. Diseña cada tarea de agente con ambas ramas, y activa la publicación en el eventodone`.- Las compuertas
postson deterministas, sin modelo. Una compuerta que falla hace fallar la ejecución. max_tool_callsybudgetson paradas duras (la sesión se cancela); un turno en el que el agente no informa uso o costo hace fallar la ejecución.concurrency: 1en tareas que comparten un repositorio;timeoutes tiempo de reloj por intento.- Los cambios de alto impacto obtienen una compuerta de aprobación (
wait+ chat) antes de publicar; el skill247-agent-taskstiene el patrón.
Presupuestos y libro de contabilidad
Cada llamada a un modelo pasa a través del puerto ctx.llm, que registra el uso (proveedor, modelo,
tokens de entrada/salida/caché, USD) en el libro de contabilidad y aplica el budget.max_usd de la ejecución
(peor caso antes de la llamada, real después: un exceso hace fallar la ejecución) y el global
budgets.daily_usd por día UTC (una vez superado, cada llamada a un modelo ese día falla rápidamente y
budget.exceeded se emite una vez, que la tarea notify debería escuchar). Un modelo sin precio falla la validación. Nunca añadas una llamada sin presupuesto, incluyendo llamadas "auxiliares" dentro
un ejecutor: usa ctx.llm, nunca un SDK directamente. Un turno agent es el único caso en el que el modelo es llamado por otra persona (el agente ACP, con su propia clave): el ejecutor
registra lo que el agente informa a través de ctx.llm.record bajo el nombre del conector y
cancela la sesión cuando el costo reportado supera el límite. oa cost --by task --since 7d
muestra el libro de contabilidad.
Estado actual
La acción llm está validada, verificada contra providers:/pricing: y
ejecutable a través de ctx.llm, con el libro de contabilidad y los presupuestos aplicados, y los tres
tipos de proveedor (anthropic, openai, openrouter) tienen adaptadores. batch: true funciona en
proveedores anthropic (Message Batches, la ejecución se estaciona hasta llm.batch.ended; consulta
references/llm-action.md). La acción decide se ejecuta
a través del mismo puerto (ctx.llm.decide()), con la API Decisions implementada en el
adaptador openrouter solamente; docs/examples/decide-triage.yaml es la referencia. La
acción agent se ejecuta sobre conectores ACP (transport: acp); docs/examples/website-updates.yaml
tareas 3, 3b y 4 son la referencia, docs/examples/connectors.d/claude.yaml el
conector; la sesión se persiste como una transcripción (oa runs logs <id>), eliminada con
la ejecución por retention.runs. mcp_servers sirve operaciones del conector a la sesión a través del
puente de herramientas del núcleo (packages/core/src/connectors/mcp-bridge.ts). model y
effort en la acción establecen las opciones de configuración ACP de la sesión (categorías model y
thought_level). work/<run_id> de una ejecución terminada es
barrido por retention.workspaces (predeterminado 7d) después de que finalice.
Al añadir un adaptador o cambiar un ejecutor, sigue references/llm-action.md,
references/decide-action.md y references/agent-action.md y mantén
docs/internal/model-actions.md, docs/internal/agent-action.md y las páginas de usuario bajo
docs/tasks/ sincronizados con el código.