Inicio / Artículos

Notas prácticas: Creación de un agente de codificación en la nube privada: OpenCode + un LLM local

Guía paso a paso para utilizar las notas prácticas: Creación de un agente de codificación en la nube privada: OpenCode + un LLM local: contratos, verificaciones y espacios para código integrable para los equipos que implementan este patrón.

Editor: REACTAPP.TOP24 min de lectura5502 palabras

Fuentes: Building a Private Cloud Coding Agent: OpenCode + a Local LLM, Running on Serverless GPUs

Las notas siguientes reconstruyen un camino práctico para “Crear un agente de codificación en nube privada: OpenCode + un LLM local, ejecutándose en GPUs serverless”. Se da énfasis a los contratos, las verificaciones y los marcadores de posición para código, en lugar de a un enfoque motivacional. Al trabajar en la etapa de descripción general, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación garantiza que los cambios posteriores en el código sean transparentes. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe indicar una única responsabilidad y no un proceso complicado.

Por qué esta arquitectura

Por qué esta arquitectura funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Considere esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de forma excesiva; los límites máximos evitan que las demostraciones se conviertan en facturas inesperadas.

Los componentes básicos

Los componentes básicos funcionan mejor cuando se tratan como una superficie medible. Registre un caso exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Anote los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de manera intensiva; los límites estrictos impiden que las demostraciones se conviertan en facturas inesperadas.

Modal

Modal funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Establezca un límite de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de manera excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas. Modal funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado.

Ollama

Para la etapa de Ollama, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Separe la construcción del cliente del bucle de mensajes para que sea posible cambiar los proveedores sin tener que reescribir la máquina de estados de la conversación.

OpenCode

Para la etapa OpenCode, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando la tarea pasa de un entorno de demostración a uno compartido. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.

El túnel

En la etapa del túnel, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben encontrarse en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto libre cuando el siguiente paso sea código o una llamada a una herramienta. En la etapa del túnel, defina las entradas, el responsable de la tarea y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar la tarea a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando una tarea falla, el error debe indicar una única responsabilidad y no un proceso complicado.

Requisitos previos y configuración

Al trabajar en la fase de requisitos previos y configuración, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta fase como un contrato entre los datos de entrada y los resultados validados. Asigne nombres a los artefactos, defina las comprobaciones de éxito y evite completaciones parciales silenciosas. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de problemas.

pip install modal
modal setup

Diseño de la imagen del contenedor

Al trabajar en la etapa de diseño de la imagen del contenedor, anote primero el contrato: entradas requeridas, señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de gastos innecesarios.

import modal

image = (
    modal.Image.from_registry(
        "nvidia/cuda:13.0.0-devel-ubuntu24.04",
        add_python="3.12",
    )
    .apt_install(
        "curl",
        "git",
        "ca-certificates",
        "zstd",
    )
    .run_commands(
        "curl -fsSL https://ollama.com/install.sh | sh",
    )
    .run_commands(
        "curl -fsSL https://opencode.ai/install | bash",
    )
    .env(
        {
            "OLLAMA_MODELS": "/models/.ollama/models",
            "OLLAMA_HOST": "127.0.0.1:11434",
            "PATH": "/root/.opencode/bin:/usr/local/bin:/usr/bin:/bin",
        }
    )
)

El script de inicio: orquestando Ollama y OpenCode

Al trabajar en la etapa de orquestación del script de inicio, anote primero el contrato: los ingresos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Registre el ID de la solicitud, el ID del modelo y la latencia en cada llamada. Sin ese registro, los errores intermitentes del proveedor parecen ser defectos de la aplicación. Al trabajar en la etapa de orquestación del script de inicio, anote primero el contrato: los ingresos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad en lugar de a un proceso complicado.

Paso 1: Iniciar Ollama en segundo plano y verificar que el proceso no se cierre de inmediato

La etapa del Paso 1 de iniciar Ollama funciona mejor si se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los archivos generados, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Fije el intérprete y el archivo de bloqueo de dependencias antes de implementar el bucle. La diferencia entre usar una computadora portátil y un entorno CI es la causa más común de fallos silenciosos en las demostraciones de API.

ollama serve > /tmp/ollama.log 2>&1 &
OLLAMA_PID=$!

sleep 3
if ! kill -0 "$OLLAMA_PID" 2>/dev/null; then
    echo "OLLAMA FAILED TO START"
    cat /tmp/ollama.log
    exit 1
fi

Paso 2: Hacer consultas hasta que la API esté realmente sirviendo datos, no solo en ejecución

La encuesta de la Etapa 2 funciona mejor cuando se trata como una métrica cuantificable. Registre un caso exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Anote los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos. Asigne un presupuesto de tokens por turno y por sesión; las herramientas agentes amplían el contexto de manera intensiva, por lo que los límites máximos evitan que las demostraciones se conviertan en facturas sorpresa.

READY=0

for i in $(seq 1 120); do
    if curl -sf http://127.0.0.1:11434/api/tags > /dev/null 2>&1; then
        READY=1
        echo "Ollama is ready."
        break
    fi
    sleep 1
done

if [ "$READY" != "1" ]; then
    echo "Ollama failed to start."
    cat /tmp/ollama.log
    exit 1
fi

Etapa 3: Extraer el modelo, pero solo si aún no está allí

La etapa 3 “Pull the stage” funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación: los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema.

if ollama list | grep -F "qwen3-coder" > /dev/null 2>&1; then
    echo "Model already exists:"
    ollama list
else
    echo "Downloading: qwen3-coder:30b"
    ollama pull "qwen3-coder:30b"
fi

La etapa 3 “Pull the stage” funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos; cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado.

Etapa 4: Clonar el repositorio con un camino de fallo real

En el Paso 4, antes de modificar el código, clona la etapa definiendo las entradas, el responsable del paso y los criterios de finalización. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Considere esta etapa como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Prefiera resultados estructurados con validación de esquema sobre textos en formato libre cuando el siguiente paso sea código o una llamada a una herramienta.

if [ ! -d /workspace/.git ]; then
    if GIT_TERMINAL_PROMPT=0 git clone \
        --branch "main" \
        --depth 1 \
        "https://github.com/yourname/yourrepo.git" \
        /workspace
    then
        echo "Clone succeeded."
    else
        echo "WARNING: git clone failed. Continuing without the repo."
        mkdir -p /workspace
    fi
else
    echo "Repository already exists."
fi

Paso 5: Escribir la configuración de OpenCode

En la fase de Escritura de OpenCode del Paso 5, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea la escritura de código o una llamada a una herramienta.

mkdir -p /root/.config/opencode
cat > /root/.config/opencode/opencode.json <<'EOF'
{
    "$schema": "https://opencode.ai/config.json",
    "model": "ollama/qwen3-coder:30b",
    "provider": {
        "ollama": {
            "npm": "@ai-sdk/openai-compatible",
            "name": "Ollama - Qwen3 Coder 30B",
            "options": {
                "baseURL": "http://127.0.0.1:11434/v1"
            },
            "models": {
                "qwen3-coder:30b": {
                    "name": "Qwen3-Coder 30B",
                    "limit": {
                        "context": 32768,
                        "output": 16384
                    }
                }
            }
        }
    }
}
EOF

Paso 6: Iniciar OpenCode en primer plano

Para la etapa Start OpenCode del Paso 6, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.

exec opencode serve \
    --hostname 0.0.0.0 \
    --port 4096 \
    --print-logs

En la fase de inicio OpenCode del Paso 6, se deben definir las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Es preferible utilizar unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado.

Conservación de los pesos del modelo

Al trabajar en la etapa de pesos de modelo persistentes, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de desperdicio.

volume = modal.Volume.from_name(
    "opencode-ollama-models",
    create_if_missing=True,
)
volumes={
    "/models": volume,
},

Creación de la sandbox

Al trabajar en la etapa de Creación del entorno aislado, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos de ejecución y el costo en tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos. Almacene en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de gastos innecesarios.

def create_sandbox():
    script = startup_script()
    secrets = []
    if GITHUB_REPO_IS_PRIVATE:
        secrets.append(modal.Secret.from_name(GITHUB_SECRET_NAME))
    sandbox = modal.Sandbox.create(
        "bash", "-lc", script,
        app=app,
        image=image,
        gpu="H100",
        cpu=8,
        memory=32768,
        secrets=secrets,
        timeout=12 * 60 * 60,
        idle_timeout=300,
        volumes={"/models": volume},
        encrypted_ports=[OPENCODE_PORT],
        workdir="/workspace",
    )
    return sandbox

Manejo de repositorios privados

Al trabajar en la etapa de Gestión de repositorios privados, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Mantén la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Almacena en caché las instrucciones del sistema estables y los esquemas de las herramientas. Reenviar un preámbulo idéntico es una causa común de desperdicio de recursos. Al trabajar en la etapa de Gestión de repositorios privados, primero escribe el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiere unidades pequeñas y probables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.

modal secret create github-token GITHUB_TOKEN=ghp_your_token_here
if GITHUB_REPO_IS_PRIVATE:
    secrets.append(modal.Secret.from_name("github-token"))
clone_url = f"https://${{GITHUB_TOKEN}}@github.com/{GITHUB_REPO}.git"

El punto de entrada local: mantener activa la zona aislada

La etapa de mantenimiento del punto de entrada local funciona mejor cuando se trata como una superficie medible. Capture una transcripción ejemplar, un caso de fallo y la nota de reversión antes de ampliar el alcance. Trate esta etapa como un contrato entre las entradas y las salidas validadas. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace completaciones parciales silenciosas. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de forma excesiva; los límites máximos evitan que las demostraciones se conviertan en facturas inesperadas.

try:
    while True:
        time.sleep(5)
        returncode = sandbox.poll()
        if returncode is not None:
            print(f"Sandbox process exited with code {returncode}.")
            break
except KeyboardInterrupt:
    print("Ctrl+C received. Terminating sandbox...")
    sandbox.terminate()
modal run --detach opencode.py
modal shell sb-abc123xyz

La implementación completa

La implementación completa funciona mejor cuando se trata como una superficie medible. Capture un registro exitoso, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Asigne un presupuesto de tokens por turno y por sesión. Las herramientas agenciales amplían el contexto de manera intensiva; los límites estrictos impiden que las demostraciones se conviertan en facturas inesperadas.

import time

import modal


# ============================================================
# Configuration
# ============================================================

APP_NAME = "opencode-ollama"

MODEL = "qwen3-coder:30b"

OPENCODE_PORT = 4096
OLLAMA_PORT = 11434

VOLUME_NAME = "opencode-ollama-models"

# Set this to the repo you want cloned into the sandbox.
# Leave as None or "" to skip cloning entirely.
GITHUB_REPO = "yourusername/your-repository"
GITHUB_BRANCH = "main"

# If GITHUB_REPO is private, create a Modal Secret containing
# a GitHub Personal Access Token (fine-grained, read-only, scoped
# to this one repo) with:
#   modal secret create github-token GITHUB_TOKEN=ghp_xxx
# and set this to True.
GITHUB_REPO_IS_PRIVATE = False
GITHUB_SECRET_NAME = "github-token"


# ============================================================
# Modal app + persistent volume
# ============================================================

app = modal.App(APP_NAME)

volume = modal.Volume.from_name(
    VOLUME_NAME,
    create_if_missing=True,
)


# ============================================================
# Container image
# ============================================================

image = (
    modal.Image.from_registry(
        "nvidia/cuda:13.0.0-devel-ubuntu24.04",
        add_python="3.12",
    )
    .apt_install(
        "curl",
        "git",
        "ca-certificates",
        "zstd",
    )
    .run_commands(
        "curl -fsSL https://ollama.com/install.sh | sh",
    )
    .run_commands(
        "curl -fsSL https://opencode.ai/install | bash",
    )
    .env(
        {
            "OLLAMA_MODELS": "/models/.ollama/models",
            "OLLAMA_HOST": "127.0.0.1:11434",
            "PATH": "/root/.opencode/bin:/usr/local/bin:/usr/bin:/bin",
        }
    )
)


# ============================================================
# Sandbox startup script
# ============================================================

def startup_script():

    repo_setup = ""

    if GITHUB_REPO:

        if GITHUB_REPO_IS_PRIVATE:
            clone_url = (
                f"https://${{GITHUB_TOKEN}}@github.com/{GITHUB_REPO}.git"
            )
        else:
            clone_url = f"https://github.com/{GITHUB_REPO}.git"

        repo_setup = f"""
echo "=============================================="
echo "Cloning repository"
echo "=============================================="
nvidia-smi || true

if [ ! -d /workspace/.git ]; then

    if GIT_TERMINAL_PROMPT=0 git clone \\
        --branch "{GITHUB_BRANCH}" \\
        --depth 1 \\
        "{clone_url}" \\
        /workspace
    then
        echo "Clone succeeded."
    else
        echo ""
        echo "=============================================="
        echo "WARNING: git clone failed."
        echo "=============================================="
        echo "This is usually because the repository is"
        echo "private (set GITHUB_REPO_IS_PRIVATE = True and"
        echo "attach a token) or the repo/branch name is"
        echo "wrong. Continuing without the repo so OpenCode"
        echo "still starts."
        echo ""
        mkdir -p /workspace
    fi

else

    echo "Repository already exists."

fi
"""

    else:

        repo_setup = """
echo "=============================================="
echo "No GitHub repository configured"
echo "=============================================="

mkdir -p /workspace
"""

    return f"""
#!/bin/bash

set -e

echo "=============================================="
echo "Modal OpenCode + Ollama"
echo "=============================================="

echo "Model: {MODEL}"
echo "OpenCode port: {OPENCODE_PORT}"
echo "Ollama port: {OLLAMA_PORT}"

# ------------------------------------------------------------
# Ollama model directory
# ------------------------------------------------------------

mkdir -p /models/.ollama/models

export OLLAMA_MODELS=/models/.ollama/models
export OLLAMA_HOST=127.0.0.1:11434

# ------------------------------------------------------------
# Start Ollama
# ------------------------------------------------------------

echo ""
echo "Starting Ollama..."

ollama serve \\
    > /tmp/ollama.log \\
    2>&1 &

OLLAMA_PID=$!

sleep 3

if ! kill -0 "$OLLAMA_PID" 2>/dev/null; then
    echo ""
    echo "=============================================="
    echo "OLLAMA FAILED TO START"
    echo "=============================================="
    echo ""
    cat /tmp/ollama.log
    echo ""
    exit 1
fi

echo "Ollama PID: $OLLAMA_PID"

# ------------------------------------------------------------
# Wait for Ollama to be ready
# ------------------------------------------------------------

echo ""
echo "Waiting for Ollama..."

READY=0

for i in $(seq 1 120); do

    if curl -sf \\
        http://127.0.0.1:11434/api/tags \\
        > /dev/null 2>&1
    then

        READY=1
        echo "Ollama is ready."
        break

    fi

    sleep 1

done

if [ "$READY" != "1" ]; then

    echo ""
    echo "Ollama failed to start."
    echo ""
    cat /tmp/ollama.log

    exit 1

fi

# ------------------------------------------------------------
# Pull the model (skipped if already present in the volume)
# ------------------------------------------------------------

echo ""
echo "=============================================="
echo "Checking Ollama model"
echo "=============================================="

if ollama list | grep -F \\
    "qwen3-coder" \\
    > /dev/null 2>&1
then

    echo "Model already exists:"
    ollama list

else

    echo ""
    echo "Model not found."
    echo ""
    echo "Downloading:"
    echo "  {MODEL}"
    echo ""
    echo "This can take some time on first startup."
    echo ""

    ollama pull "{MODEL}"

fi

echo ""
echo "=============================================="
echo "Available Ollama models"
echo "=============================================="

ollama list

# ------------------------------------------------------------
# Repository
# ------------------------------------------------------------

{repo_setup}

cd /workspace

# ------------------------------------------------------------
# OpenCode configuration
# ------------------------------------------------------------

mkdir -p /root/.config/opencode

cat > /root/.config/opencode/opencode.json <<'EOF'
{{
    "$schema": "https://opencode.ai/config.json",

    "model": "ollama/{MODEL}",

    "provider": {{
        "ollama": {{
            "npm": "@ai-sdk/openai-compatible",

            "name": "Ollama - Qwen3 Coder 30B",

            "options": {{
                "baseURL": "http://127.0.0.1:11434/v1"
            }},

            "models": {{
                "{MODEL}": {{
                    "name": "Qwen3-Coder 30B",

                    "limit": {{
                        "context": 32768,
                        "output": 16384
                    }}
                }}
            }}
        }}
    }}
}}
EOF

echo ""
echo "=============================================="
echo "OpenCode configuration"
echo "=============================================="

cat /root/.config/opencode/opencode.json

# ------------------------------------------------------------
# Start OpenCode
# ------------------------------------------------------------

echo ""
echo "=============================================="
echo "Starting OpenCode server"
echo "=============================================="

exec opencode serve \\
    --hostname 0.0.0.0 \\
    --port {OPENCODE_PORT} \\
    --print-logs
"""


# ============================================================
# Create Sandbox
# ============================================================

def create_sandbox():

    script = startup_script()

    secrets = []

    if GITHUB_REPO_IS_PRIVATE:
        secrets.append(modal.Secret.from_name(GITHUB_SECRET_NAME))

    sandbox = modal.Sandbox.create(
        "bash",
        "-lc",
        script,

        app=app,

        image=image,

        gpu="H100",

        cpu=8,

        memory=32768,

        secrets=secrets,

        timeout=12 * 60 * 60,

        idle_timeout=300,

        volumes={
            "/models": volume,
        },

        encrypted_ports=[
            OPENCODE_PORT,
        ],

        workdir="/workspace",
    )

    return sandbox


# ============================================================
# Local entrypoint
# ============================================================

@app.local_entrypoint()
def main():

    print()
    print("==============================================")
    print("Creating Modal Sandbox")
    print("==============================================")
    print()

    sandbox = create_sandbox()

    print()
    print("Sandbox created:")
    print(sandbox.object_id)

    print()
    print("Waiting for OpenCode server...")
    print()

    time.sleep(5)

    tunnel_url = None

    try:

        tunnels = sandbox.tunnels()
        tunnel = tunnels[OPENCODE_PORT]
        tunnel_url = tunnel.url

        print()
        print("==============================================")
        print("OPEN CODE IS READY")
        print("==============================================")
        print()
        print("Sandbox ID (save this to reconnect):")
        print(sandbox.object_id)
        print()
        print("Browser:")
        print()
        print(tunnel_url)
        print()
        print("Your OpenCode server is backed by:")
        print()
        print("  Ollama")
        print("  Qwen3-Coder 30B")
        print()
        print("Ollama endpoint (inside the sandbox only):")
        print("http://127.0.0.1:11434")
        print()
        print("==============================================")
        print()

    except Exception as e:

        print()
        print("Sandbox started, but tunnel information")
        print("could not be retrieved automatically.")
        print()
        print(f"Error: {e}")
        print()
        print("The Sandbox ID is:")
        print(sandbox.object_id)

    print("Sandbox is running. Press Ctrl+C to stop it.")
    print("(Run with `modal run --detach opencode.py` to keep")
    print(" it alive after you disconnect.)")
    print()

    try:
        while True:
            time.sleep(5)

            returncode = sandbox.poll()

            if returncode is not None:
                print(f"Sandbox process exited with code {returncode}.")
                break

    except KeyboardInterrupt:
        print()
        print("Ctrl+C received. Terminating sandbox...")
        sandbox.terminate()
        print("Sandbox terminated.")

Ejecutarlo

Funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Establezca un límite de tokens por turno y por sesión. Las herramientas agentes amplían el contexto de manera excesiva; los límites estrictos evitan que las demostraciones se conviertan en facturas inesperadas. Funciona mejor cuando se trata como una superficie medible. Capture un registro ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el fallo debe apuntar a una sola responsabilidad y no a un proceso complicado.

modal run opencode.py
==============================================
OPEN CODE IS READY
==============================================

Sandbox ID (save this to reconnect):
sb-abc123xyz

Browser:

https://your-generated-subdomain-4096.modal.host
modal run --detach opencode.py
modal shell sb-abc123xyz
sandbox = modal.Sandbox.from_id("sb-abc123xyz")
sandbox.terminate()

Ampliando el sistema

En la fase de ampliación del sistema, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Prefiera resultados estructurados con validación de esquema sobre textos en formato libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.

Consideraciones de seguridad

En la fase de consideraciones de seguridad, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea la escritura de código o una llamada a una herramienta.

Conclusión

En la fase de Conclusión, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto en formato libre cuando el siguiente paso sea código o una llamada a una herramienta. En la fase de Conclusión, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado.

Lista de verificación operativa

En la fase de lista de verificación operativa, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto.

Documente tanto la ruta óptima como la ruta de recuperación. Las intentonas repetidas, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son algo que se añada posteriormente.

Prefiera salidas estructuradas con validación de esquema en lugar de texto libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.

Establezca puntos de control después de los pasos costosos. La función de reanudación no debe volver a facturar la misma llamada al LLM cuando un operador intente nuevamente un nodo posterior.

Fije las versiones de las dependencias y registre el resumen de la imagen que se utilizó para ejecutar la demostración. La reproducibilidad es mejor que el conocimiento basado en prácticas internas.

Registra los tiempos de ejecución y el costo de los tokens o consultas junto con los resultados funcionales. Ver la información sobre costos desde el principio evita facturas inesperadas cuando se pasa de entornos de demostración a entornos compartidos.

Antes de promocionar la solución, congela las versiones, guarda una transcripción de referencia para el camino crítico y confirma los pasos para revertir cambios. Los entornos compartidos requieren límites de uso, verificaciones de asignación y un responsable claro para la rotación de credenciales secretas. Prefiere una fiabilidad sencilla a demostraciones ingeniosas pero puntuales.

Nota para el lote 0b5d2687b03c: mantén las claves del proveedor fuera del repositorio, establece un límite para los tokens por sesión y almacena las transcripciones junto a los archivos de prueba para que los cambios en el modelo posterior sigan siendo comparables.

Para la fase 0 de las notas de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben encontrarse en un lugar que los operadores puedan auditar sin necesidad de leer todo el sistema.

Detalle de fortalecimiento 0/810: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en observaciones anecdóticas.

Al trabajar en la primera etapa de las notas de fortalecimiento, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y verificables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado.

Detalle de fortalecimiento 1/810: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

La nota de fortalecimiento 2 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Registre los tiempos y el costo en tokens o consultas junto con los resultados funcionales. La visibilidad temprana de los costos evita facturas inesperadas cuando el proceso pasa de la demostración a entornos compartidos.

Detalle de fortalecimiento 2/810: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Para la fase 3 de la nota de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

Detalle de fortalecimiento 3/810: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Al trabajar en la fase 4 de las notas de fortalecimiento, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta fase como un contrato entre los datos de entrada y los resultados validados. Asigne nombres a los artefactos, defina las comprobaciones de éxito y rechace las completaciones parciales silenciosas.

Detalle de fortalecimiento 4/810: mida el tiempo de ejecución, la clase del error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

La nota de fortalecimiento 5 funciona mejor cuando se trata como una superficie medible. Capture una transcripción de referencia, un caso de fallo y la nota de reversión antes de ampliar el alcance. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar que los operadores puedan auditar sin tener que leer todo el sistema.

Detalle de fortalecimiento 5/810: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Para la fase 6 de la nota de fortalecimiento, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad en lugar de a un proceso complicado.

Detalle de fortalecimiento 6/810: mida el tiempo de ejecución, la clase de error y el consumo de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Al trabajar en la etapa 7 de las notas de fortalecimiento, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la fase de demostración a entornos compartidos.

Detalle de fortalecimiento 7/810: mida el tiempo empleado, la clase del error y el gasto en tokens para esta nota, y luego decida si mantener la modificación basándose en un conjunto fijo de preguntas en lugar de en observaciones anecdóticas.

La nota de fortalecimiento 8 funciona mejor cuando se trata como una superficie medible. Capture una transcripción ideal, un caso de fallo y la nota de reversión antes de ampliar el alcance. Documente tanto el camino óptimo como el camino de recuperación. Los intentos repetidos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores.

Detalle de endurecimiento 8/810: mida el tiempo de procesamiento de la pared, la clase de error y el gasto de tokens para esta nota, y luego decida si mantener el cambio basándose en un conjunto fijo de preguntas en lugar de en anécdotas.

Marcador de reescritura 1 para 0b5d2687b03c: reformule las afirmaciones circundantes en lenguaje de operador, mantenga intactas las posiciones [[CODE_n]] y evite repetir frases del texto original.

Marcador de reescritura 2 para 0b5d2687b03c: reformule las afirmaciones circundantes en lenguaje de operador, mantenga intactas las posiciones [[CODE_n]] y evite repetir frases del texto original.

Marcador de reescritura 3 para 0b5d2687b03c: reformule las afirmaciones circundantes en lenguaje de operador, mantenga intactas las posiciones [[CODE_n]] y evite repetir frases del texto original.

Marcador de reescritura 4 para 0b5d2687b03c: reformule las afirmaciones circundantes en lenguaje de operador, mantenga intactas las posiciones [[CODE_n]] y evite repetir frases del texto original.

Escribe de nuevo el marcador 5 para 0b5d2687b03c: reformula las afirmaciones circundantes en lenguaje de operadores, mantiene intactas las casillas [[CODE_n]] y evita repetir las frases del texto original.

Escribe de nuevo el marcador 6 para 0b5d2687b03c: reformula las afirmaciones circundantes en lenguaje de operadores, mantiene intactas las casillas [[CODE_n]] y evita repetir las frases del texto original.

Escribe de nuevo el marcador 7 para 0b5d2687b03c: reformula las afirmaciones circundantes en lenguaje de operadores, mantiene intactas las casillas [[CODE_n]] y evita repetir las frases del texto original.

Escribe de nuevo el marcador 8 para 0b5d2687b03c: reformula las afirmaciones circundantes en lenguaje de operadores, mantiene intactas las casillas [[CODE_n]] y evita repetir las frases del texto original.

Escribe de nuevo el marcador 9 para 0b5d2687b03c: reformula las afirmaciones circundantes en lenguaje de operadores, mantiene intactas las casillas [[CODE_n]] y evita repetir las frases del texto original.

Redacte nuevamente el marcador 10 para 0b5d2687b03c: reformule las afirmaciones circundantes en lenguaje de operadores, mantenga intactas las casillas [[CODE_n]], y evite repetir las frases originales.