O Caminho das Pedras

El motor v2 vuelve solo en OpenWork: la política del Den fuerza la migración y el techo real es la 0.18.58

2026-10-29

El chat está en el motor v1, las actualizaciones automáticas están apagadas, y el v2 vuelve igual. Revertir en el selector dura unos minutos; reiniciar la app devuelve el v2; y a veces el cambio ocurre en medio de un turno, sin clic y sin aviso.

La escena desmonta una premisa que parecía sólida: que fijar la versión de la app y apagar las actualizaciones vuelve local la elección del motor. Lo es — hasta una versión concreta. De ahí en adelante el escritorio consulta una política remota cada 60 segundos, y esa política tiene prioridad sobre el selector, sobre el archivo de estado y sobre la variable de entorno que debería desactivar la migración.

Corrección al artículo anterior. El análisis de la reversión a 0.18.59 + motor v1 recomendaba la 0.18.59 como techo. Para el free eso sigue siendo cierto — hasta la 0.18.59 los gratuitos sirven. Pero la 0.18.59 es exactamente la primera versión que obedece a la política de motor, y por eso no sostiene el motor. El techo que cumple los dos requisitos es la 0.18.58.


1. Qué se rompió

El síntoma tiene tres caras, y todas apuntan al mismo origen:

El rastro está en %APPDATA%\openwork\engine-v2-preview.json:

{
  "enabled": true,
  "chatRouting": true,
  "engineOrigin": "forced",
  "engineForced": true,
  "engineChangedAt": "2026-10-29T10:32:37.009Z",
  "historyCopyResult": { "imported": 12, "skipped": 89, "total": 101 }
}

engineOrigin: "forced" no es un valor que escriba el selector. Aparece cuando nadie eligió — y es el único campo que separa "cambié de motor" de "lo cambiaron por mí".

En el localStorage de la app, la clave openwork.engine-migration.analytics.v1 guarda la hora de cada cambio (migration_started y migration_completed). Fue lo que dató las dos migraciones de la misma madrugada, a las 00:27 y 07:25 de Brasília, en una app detenida, que no se había actualizado.


2. La trampa central: son tres palancas, no dos

El diagnóstico anterior separaba dos: la versión de la app decide el free, el motor decide el archivado. Faltaba la tercera — la que decide el motor sin pasar por el usuario.

Síntoma Qué controla de verdad Por qué el intento obvio no aguanta
Los modelos gratuitos responden 403 FreeTierError Versión de la app (a partir de la 0.18.60) Revertir el motor no toca la versión
El botón de archivar desaparece y la escritura devuelve 501 Motor (el v2 no archiva) Cambiar la versión no cambia el motor que atiende
El v2 vuelve solo, con las actualizaciones apagadas Política remota, consultada cada 60 s por el escritorio — y la versión de la app decide si obedece Apagar la actualización automática es otro canal; esta orden llega en runtime

La consecuencia práctica es que "sostener la versión" y "elegir el motor" dejaron de ser la misma decisión. Un pin de versión protege contra la rotura del free; no protege contra la migración de motor, porque la migración no es una actualización.


3. Cómo funciona el mecanismo por dentro

El automation-runner del escritorio consulta el Den cada 60 s (RUNNER_WORK_POLL_MS = 60_000) y, en la respuesta de trabajo, recibe un campo enginePolicy: { upgrade: boolean }. La descripción del esquema es explícita:

"Live org policy is returned even when inventory has not changed."

Cuando llega true, el escritorio llama POST /experimental/engine-v2-preview/force en el servidor local, con el token de host. El servidor entonces:

  1. agenda el upgrade en background;
  2. espera a que el motor quede ocioso;
  3. copia el historial a la base del v2, con backup;
  4. graba chatRouting: true y engineOrigin: "forced".

Revertir a mano solo está protegido contra el camino de revert automático. La siguiente consulta, como máximo 60 segundos después, rearma el upgrade — y eso produce la impresión de "vuelve solo mientras trabajo".

El corte es la versión de la app, y es estrecho

Comparando el fuente del repositorio different-ai/openwork por tag (v0.18.54 a v0.18.63):

Versión enginePolicy en el runner · setForcePolicy en el servidor ¿La migración de motor es predeterminada local?
≤ 0.18.58 ausentes no
0.18.59 · 0.18.60 presentes no (OPENWORK_ENGINE_AUTO_UPGRADE === "1")
≥ 0.18.61 presentes sí (!== "0") — y la política del Den anula el apagado

El archivo apps/desktop/electron/engine-force-policy.mjs y la función setForcePolicy simplemente no existen en la 0.18.58. Ahí la app ignora la política porque no tiene con qué aplicarla. Es el mismo motivo por el que, a partir de la 0.18.61, OPENWORK_ENGINE_AUTO_UPGRADE=0 no sostiene nada: la política llega después.

La nota de versión de la 0.18.60 (22/10) anuncia el cambio con nombre: "Older installs move to OpenCode v2 in the background."

Por qué editar el archivo de estado no lo resuelve

Con la app abierta, el servidor mantiene chatRouting en memoria y reescribe el archivo en cualquier persistState. Escribir false con él en marcha se pierde en el ciclo siguiente. La edición solo vale con la app cerrada, y antes de cambiar la versión.


4. Paso a paso verificable

El objetivo es la única combinación en que el free sirve y el motor es elección local: app ≤ 0.18.58, actualizaciones apagadas, chat en el v1.

Paso 1 — Confirmar quién manda

Get-Content "$env:APPDATA\openwork\engine-v2-preview.json" -Raw

chatRouting: true con engineOrigin: "forced" es política remota actuando, no una elección propia. La comprobación en vivo está en el servidor local del workspace — el puerto en %APPDATA%\openwork\openwork-server-state.json, y el token de cliente en %APPDATA%\com.differentai.openwork\openwork-server-tokens.json (son secretos de sesión: úsalos en memoria, nunca los escribas en un archivo de proyecto):

GET http://127.0.0.1:<puerto>/experimental/engine-v2-preview/status

La respuesta trae running, version y chatRouting con el mismo origen forced cuando la política está aplicada.

Paso 2 — Cerrar la app y fijar el estado

Solo con OpenWork cerrado:

{
  "enabled": true,
  "chatRouting": false,
  "engineOrigin": "manual"
}

enabled: true por sí solo no cambia el motor; lo que manda es chatRouting.

Paso 3 — Instalar una versión ≤ 0.18.58

Antes de buscar el instalador fuera de la máquina, mira la caché del actualizador: el installer.exe de la raíz de %LOCALAPPDATA%\@openworkdesktop-updater\ es la última versión que instaló, y es lo que app-recovery usa como rollback.

(Get-Item "$env:LOCALAPPDATA\@openworkdesktop-updater\installer.exe").VersionInfo.FileVersion

Instala la 0.18.58 y mantén las actualizaciones automáticas apagadas.

Paso 4 — Comprobar que funcionó

(Get-Item "$env:LOCALAPPDATA\Programs\@openworkdesktop\OpenWork.exe").VersionInfo.FileVersion
# esperado: 0.18.58

Paso 5 — Barrera contra la subida accidental

La actualización se descarga en %LOCALAPPDATA%\@openworkdesktop-updater\pending\ (openwork-win-x64-<versión>.exe + update-info.json) y no se aplica sola. Mantener esa carpeta vacía es lo que impide que la versión suba sin intención.


5. Trampas posteriores a la bajada

engineOrigin: "forced" puede ser residuo. En la 0.18.58 no hay cliente de política que la reaplique; el campo que quedó en el archivo es herencia de la versión anterior. Antes de tratarlo como política activa, comprueba la versión instalada y el running del endpoint — el residuo no se combate con ediciones.

POST .../force con {"upgrade": false} no es una solución. Vale hasta la siguiente consulta, como máximo 60 segundos. Sin la versión que no conoce la política, la orden vuelve.

El aviso del Connect es un falso positivo. OpenWork Connect: Needs attention · The selected provider/model was not found in OpenCode provider catalog. persiste con el motor v1 aunque el chat y las tools funcionen: el sync del Cloud graba el predeterminado en un modelo del AI Gateway (ipr_<id>/gwm_<id>), que no existe en el catálogo del v1, y la interfaz no tiene control para cambiar ese campo. Desaparece durante el checking y vuelve enseguida.

Una sesión que atravesó los dos motores puede rechazar toda escritura. Archivar, renombrar y borrar devuelven Unexpected server error. Check server logs for details. con un ref=err_…, y nada cambia. No es la bajada: es el contador de eventos de la base del v1 atrasado respecto del último evento de la sesión. La firma está en %USERPROFILE%\.local\share\opencode\log\opencode.log, en la línea del ref — UNIQUE constraint failed: event.aggregate_id, event.seq dentro de Session.setArchived. Medir antes de corregir: select max(seq), count(*) from event where aggregate_id='<ses_id>' contra select seq from event_sequence where aggregate_id='<ses_id>'. Contador menor que el último seq es este caso.

Las conversaciones creadas en el v2 no aparecen en el v1. La base del v2 sigue en %APPDATA%\openwork\opencode-v2\state\opencode.db y nada se borra; lo que hay que copiar antes de cambiar de motor es el texto.

El rollback automático devuelve la 0.18.59. Si la 0.18.58 falla al arrancar, app-recovery usa el installer.exe de la raíz como fallback — y la 59 es justamente la primera versión que obedece a la política. Tras una recuperación, comprueba la versión antes de concluir que la bajada no funcionó.

Apagar el runner corta más que la política. OPENWORK_AUTOMATION_RUNNER=off es la contingencia para quien quiera quedarse en la 0.18.59: corta el canal de la política, pero el escritorio deja de aceptar Automations y comandos de sesión remota. Cambia funcionalidad por control.


6. Trade-off y el disparador de salida

Lo que se paga por quedarse en la 0.18.58: se pierden las correcciones de la 0.18.59, y la app se aleja del producto en cada release. El motor v1 está en descontinuación (#5962, #6121) — es una posición con plazo, no un destino.

El disparador de salida es objetivo: el free volver a servir en una versión ≥ 0.18.60 (seguido en los issues #6466 y #6467). Ese día, actualizar deja de ser un movimiento aislado:

La consecuencia práctica: la versión dejó de ser solo la palanca de lo que se rompe — también decide si una política remota puede cambiar el motor por debajo. Quien sostiene una versión como control tiene que responder dos preguntas, no una: cuál es el piso que devuelve el recurso, y cuál es el techo en el que la app todavía obedece solo a ti. En este caso las dos respuestas caen en el mismo número: 0.18.58.

Bruno Torres

Bruno Torres

Emprendedor digital solo. Construyo negocios digitales desde cero con agentes de IA — y documento el camino, incluido lo que no funcionó.

Comentarios

Compartir: X · Facebook · LinkedIn · WhatsApp · Telegram · E-mail ·