Shadow, canary y feature flags en agentes: soltar sin redeploy
Resumen
Shadow corre el candidato sin servir; canary sirve un porcentaje; el flag libera sin redeploy. Distinto de preview, de kill switch y de fallback de modelo. Cloudflare Gradual Deployments, Vercel Flags y AWS AppConfig. Cero side-effects en shadow. Canary con afinidad de versión y rollback. Flags no son secretos ni config de arranque.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Un agente nuevo no se “suelta” con un merge a main. Se suelta cuando deja de ser el único camino. Tres palancas: shadow (corre y no sirve), canary (sirve un %), feature flag (el código ya está y un interruptor decide). No son preview vs production. No son kill switch. No son fallback de modelo: el fallback cambia de cerebro ante 5xx, no de comportamiento.
Contrato: shadow no escribe. Canary tiene rollback y afinidad. El flag no guarda secretos ni hostnames. Liberar ≠ desplegar.
Qué hace cada palanca
| Palanca | Quién ve el cambio | Side-effects del candidato | Cuándo usarla |
|---|---|---|---|
| Shadow | Nadie. Solo logs y evals | Cero. Dry-run / cola muerta | Comparar tools, JSON, latencia contra el control |
| Canary | Un % de requests reales | Sí, en ese % | Subir tráfico cuando shadow ya empató calidad |
| Flag | Quien matchee targeting | Sí, si el flag está on | Encender, apagar o segmentar sin redeploy |
| Kill switch | Todos off | No | Incidente. Distinto: kill switch |
| Breaker | Esa dependencia open | No | Fallo repetido. Distinto: circuit breaker |
Vercel Flags (docs, last_updated 2026-08-11; /docs/flags.md HTTP 200): Roll out features gradually… Test in production safely… Ship and deploy independently from releasing features. Cloudflare Gradual deployments (last updated 2026-07-03): splitting traffic across versions con wrangler versions upload (versión sin servir) y wrangler versions deploy (split). AWS AppConfig (Last-Modified 2026-09-04) separa flags de freeform configuration y, al desplegar, usa StartDeployment con estrategia, GrowthFactor y FinalBakeTimeInMinutes — si un alarm de CloudWatch dispara, rollback.
Shadow: dos cerebros, una boca
El control sirve. El candidato repite la misma petición (prompt, tools, tenant) y tira el resultado a un sink: log, dataset de eval, cola que nadie consume. Si el candidato manda un email, cobra o escribe memoria, no es shadow: es un canary disfrazado.
Reglas:
- Misma entrada. Copia el payload; no “simplifiques” tools.
- Sin I/O. Tools de escritura → stub o
dry_run=true. Lecturas sí, con techo de costo. - Timeout propio. El shadow no puede alargar el p95 del control.
AbortSignalal candidato; el usuario no espera. - Comparar, no fusionar. Diff de tool calls, JSON schema, tokens, latencia. Un LLM-as-judge puede puntuar; el veredicto no decide el serve.
- Tenant y PII. El shadow hereda ACL. No copies PII a un bucket “de debug”.
Señal de salida de shadow: N trazas con el mismo contrato (tools, schema, error rate) y un delta de latencia que cabe en el error budget. Entonces canary, no “un martes a producción”.

Canary: un porcentaje que sí escribe
Canary sirve el candidato a una fracción. Cloudflare lo hace con versiones: versions upload crea la versión; versions deploy pregunta porcentajes. El propio doc avisa version skew: cada request se rutea independiente, así que recargas consecutivas pueden caer en versiones distintas. Si el agente es conversacional (turnos, memoria, Durable Object), usa version affinity o un override; si no, el usuario ve un toolset que cambia a mitad de chat.
AWS AppConfig no “hace canary de código”: despliega configuración (flags o freeform) con GrowthType lineal/exponencial, GrowthFactor por intervalo y FinalBakeTimeInMinutes de horneado. Un alarm → rollback. Eso es canary de flag, no de binario.
Checklist canary:
- Techo pequeño y explícito. 1% → 5% → 25% → 100%. No “un rato”.
- Afinidad. Misma sesión / mismo
thread_id→ misma versión. - SLIs del canary, no del blend. Error rate, tool-fail, p95, costo/request etiquetados por versión. Cloudflare Logpush trae
ScriptVersion; Vercel Flags manda evaluaciones a Runtime Logs / Web Analytics (/docs/flags/observability). - Rollback en un comando. Cloudflare:
versions deployal 100% de la versión previa. AppConfig: el alarm lo hace. Un flag de release: default = off. - Cero canary de writes irreversibles (cobros, correos masivos, deletes) hasta que shadow + 1% estén verdes.
Durable Objects: only one version of each Durable Object can run at a time. El canary de un DO no es un split por request.
Feature flags: el código ya está, el interruptor no
El flag vive después del deploy. Vercel: Ship and deploy independently from releasing features. Targeting, segments, splits. Marketplace (LaunchDarkly, Statsig, Split) o Vercel Flags. Flags Explorer overridea en local sin tocar a los demás.
LaunchDarkly — Creating flags (HTTP 200, 2026-09-06) clasifica:
- Release — temporal; se archiva al 100%.
- Kill switch — permanente; emergency shutoff. En este sitio el contrato de apagado está en kill switch, no lo reimplementes en un boolean suelto.
- Experiment — temporal; se archiva al elegir ganador.
- Operational / entitlement — permanentes (banner, plan).
Default: la variación segura es off. Si deprecas un feature, invierte: on = false. Nombres con efecto (agent.tool.web_search, no tmp2). Un flag, una cosa. Dependencias = prerequisite, no if anidados en el prompt.
Cuándo no usar flags (LaunchDarkly, misma guía):
- Secretos o credenciales. Eso es secretos.
- Config que, si se apaga, impide arrancar (hostname de Postgres, URL de API).
- Base de datos / JSON enorme. Parte el flag.
- Cada commit. Envuelve el feature, no el diff.
AppConfig multi-variant: el request manda contexto; reglas eligen variación. Tenant/plan sí; el system prompt entero, no.

Cómo se ve en un agente
type Lane = "control" | "shadow" | "canary";
async function handleTurn(req: AgentRequest, flag: Flags) {
const lane: Lane = pickLane(req, flag); // sticky por thread_id
const control = runAgent(req, "control");
if (lane === "shadow") {
void runAgent({ ...req, sink: "shadow", dryRun: true }, "candidate")
.catch((err) => logShadowFail(err));
return control; // el usuario solo ve control
}
if (lane === "canary") return runAgent(req, "candidate");
return control;
}
El flag decide el lane, no el modelo. Si el primario 503, eso sigue siendo fallback dentro del lane. No mezcles “cambio de prompt” con “cambio de proveedor” en el mismo boolean.
Versiona el toolset (toolset_version) aparte del flag de release: un canary con schema breaking es un versionado de tools, no un 5%.
Plataformas, sin adivinar números
| Pieza | Qué te da | Qué no te da |
|---|---|---|
| Cloudflare versions | Upload sin servir; split %; ScriptVersion en Logpush; affinity / overrides | Shadow automático. Tú duplicas la invocación |
| Vercel Flags | Release ≠ deploy; Explorer; observability de evaluaciones | Canary de Workers/Functions por sí solo |
| AWS AppConfig | Flags + freeform; StartDeployment; bake + alarm → rollback | Sustituto de secretos (aunque puede leer Secrets Manager: no lo uses como flag) |
| LaunchDarkly | Tipos de flag, prerequisites, “when not to use” | Licencia. El contrato vale igual con un boolean bien hecho |
Wrangler: mínimo 3.40.0 para versions. Gradual solo con las últimas 100 versiones subidas.
Checklist
- Shadow:
dry_run, timeout propio, misma entrada, cero writes. - Canary: techo %, sticky
thread_id, SLI por versión, rollback ensayado. - Flag: default off, un propósito, sin secretos, sin hostname de boot.
- Release flags: fecha de archivo. Permanentes: kill / entitlement, no “tmp”.
- Logs:
lane,flag_key,version_id. Sin PII. - Tool schema: aditivo. Breaking → version, no canary.
- Kill switch distinto del flag de release. Breaker distinto del canary.
FAQ
¿Preview no es un canary? No. Preview es otro deploy, otra URL, a menudo sin cron ni webhooks reales. El canary vive en producción con tráfico real.
¿Puedo shadow-ear un tool que cobra? Solo si el adapter tiene dry_run verificado (no un if en el prompt). Si el proveedor no tiene dry-run, no hay shadow: hay staging.
¿El flag reemplaza al kill switch? No. El kill switch es fail-closed y permanente. Un release flag se archiva. Si los mezclas, el día del incidente nadie sabe cuál apagar.
¿Un 50/50 es un experimento? Sí, si hay métrica y parada. Si no hay métrica, es un coin-flip en prod. Archive al elegir ganador.
Siguiente paso
Empieza por un tool de escritura. Shadow 24 h. Si el diff de tools es ruido, no subas canary. Al 1% verde, el flag de release: off → 5% → 100% y se archiva. Sigue el hub de operación y /curso/instalar-agente.
Lecturas relacionadas
Sigue explorando AgentOps y otras piezas para builders.

Dead-letter queue en agentes: el mensaje que no debe reintentarse

Fallback de modelos en agentes: otro modelo, no el mismo 429

SLO y error budget en agentes: 4 SLIs, burn rápido/lento y política 50/100
