Practical notes: Создание агента для кодирования в частном облаке: OpenCode + локальный LLM
Пошаговое руководство по Practical notes: Создание агента для кодирования в частном облаке: OpenCode + локальный LLM: контракты, проверки и слоты для кода для команд, использующих эту схему.
Источники: Building a Private Cloud Coding Agent: OpenCode + a Local LLM, Running on Serverless GPUs
В следующих заметках описывается практический подход к созданию агента для программирования в частном облаке: OpenCode в сочетании с локальным большим языком модели, работающим на серверных GPU. Основное внимание уделяется контрактам, проверкам и шаблонам кода, которые можно легко вставить, а не мотивирующим формулировкам. На этапе обзора сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. Если какой-то шаг не сработает, причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.
Почему именно такая архитектура
Почему такая архитектура работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задач. Установите лимиты на количество токенов за ход и за сессию. Инструменты агентов активно расширяют контекст; строгие ограничения предотвращают превращение демонстраций в неожиданные счёты.
Основные компоненты
Элементы конструкции работают наилучшим образом, если рассматривать их как измеримую поверхность. Зафиксируйте один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Записывайте время выполнения операций, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-версии к общедоступным средам. Определите лимит токенов на один ход и на одну сессию. Инструменты агентного типа активно расширяют контекст; жесткие ограничения не позволяют демо-версиям превращаться в неожиданные счета.
Модальные окна
Modal работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Установите лимиты на количество токенов за раунд и сессию. Инструменты агентного типа активно расширяют контекст; строгие ограничения предотвращают появление неожиданных счетов. Modal работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, проверяемые единицы кода вместо обширных скриптов. Когда какой-то шаг сбивается, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Ollama
Для этапа Ollama необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия файлов, определите критерии успеха и не допускайте молчаливого частичного завершения работы. Отделите процесс создания клиента от цикла обработки сообщений, чтобы можно было заменять поставщиков без переписывания машины состояний диалога.
OpenCode
На этапе OpenCode необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение стоимости заранее помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. При следующем шаге, представляющем собой код или вызов инструмента, следует отдавать предпочтение структурированным выводам с проверкой по схеме перед свободным текстовым описанием.
Туннель
На этапе туннеля необходимо определить входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, которое операторы могут проверять, не читая весь граф обработки. Предпочитайте структурированные выходные данные с проверкой схемы вместо свободного текста, когда следующим шагом является код или вызов инструмента. На этапе туннеля необходимо определить входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага он должен указывать на конкретную причину, а не на запутанную структуру обработки.
Предварительные требования и настройка
При работе над этапом предварительных требований и настройки сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успешности и не допускайте молчаливого частичного выполнения задачи. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых данных является распространенной причиной избыточных ресурсов.
pip install modal
modal setup
Проектирование образа контейнера
При работе над этапом проектирования образа контейнера сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Рядом с функциональными результатами занесите информацию о времени выполнения и стоимости токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Сохраняйте в кэше стабильные системные инструкции и схемы инструментов. Повторная отправка одинаковых данных является частой причиной лишних затрат.
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",
}
)
)
Скрипт запуска: координация работы Ollama и OpenCode
При работе над этапом оркестрации сценария запуска сначала опишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Записывайте ID запроса, ID модели и время задержки при каждом вызове. Без такой отчетности периодические ошибки поставщика могут выглядеть как баги приложения. При работе над этапом оркестрации сценария запуска сначала опишите контракт: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой список помогает сохранять честность последующих изменений в коде. Воздерживайтесь от использования обширных скриптов в пользу небольших, тестируемых единиц. Когда какой-то шаг срывается, причина неудачи должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Шаг 1 — Запустите Ollama в фоновом режиме и убедитесь, что процесс не остановился сразу
Этап запуска Ollama в рамках Шага 1 будет работать наилучшим образом, если рассматривать его как измеримую основу. Сохраните один успешный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия соответствующим файлам, определите критерии успеха и не допускайте безответных частичных завершений. Заблокируйте интерпретатор и файлы зависимостей до начала работы цикла. Различия в условиях работы на ноутбуке и в среде CI являются наиболее частой причиной скрытых сбоев при демонстрации 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
Шаг 2 — Проверяйте состояние до тех пор, пока API действительно не будет предоставлять данные, а не просто работать
Опрос на втором этапе до достижения определенной стадии работает наилучшим образом, если рассматривать его как измеримую характеристику. Соберите один пример успешной работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Записывайте время выполнения операций, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-версии к общедоступным средам. Установите лимиты на количество токенов за один ход и за сессию. Инструменты типа агентов активно расширяют объем обрабатываемой информации; жесткие ограничения помогают избежать неожиданных счетов при использовании демо-версий.
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
Шаг 3 — Загрузить модель, но только если ее там еще нет
Этап 3 «Подтягивание на сцену» работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Установите лимиты на количество токенов за раунд и сессию. Инструменты-агенты активно расширяют контекст; строгие ограничения предотвращают появление неожиданных счетов при демонстрациях.
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
Этап 3 «Подтягивание на сцену» работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг сбивается, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Этап 4 — Клонирование репозитория с реальным путём сбоя
На этапе 4, предназначенном для клонирования сцены, необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывания скрытого состояния. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия создаваемых файлов, определите критерии успешного выполнения и не допускайте молчаливого частичного завершения работы. При следующем шаге, представляющем собой написание кода или вызов инструмента, предпочтительнее использовать структурированные выходные данные с проверкой по шаблону вместо свободного текста.
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
Шаг 5 — Написание конфигурации OpenCode
На этапе письма кода OpenCode в рамках шага 5 необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение стоимости заранее помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. При следующем шаге, представляющем собой написание кода или вызов инструмента, предпочтительнее использовать структурированные выводы с проверкой схемы вместо свободного текста.
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
Шаг 6 — Запуск OpenCode в первом плане
Для этапа Start OpenCode шага 6 необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища конфиденциальных данных и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. При следующем шаге, представляющем собой код или вызов инструмента, следует предпочитать структурированные выходные данные с проверкой схемы вместо свободного текста.
exec opencode serve \
--hostname 0.0.0.0 \
--port 4096 \
--print-logs
На этапе Start OpenCode шага 6 необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не пытаясь угадать скрытое состояние. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага причина должна быть связана с конкретной функцией, а не с запутанной структурой обработки данных.
Хранение весов модели
При работе над этапом сохранения весов модели сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым элементам, определите критерии успешности и не допускайте молчаливого частичного завершения работы. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых данных является распространенной причиной ресурсозатрат.
volume = modal.Volume.from_name(
"opencode-ollama-models",
create_if_missing=True,
)
volumes={
"/models": volume,
},
Создание песочницы
При работе над этапом создания песочницы сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Храните в кэше стабильные инструкции системы и схемы инструментов. Повторная отправка одинаковых данных — частая причина избыточных затрат.
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
Работа с приватными репозиториями
При работе над этапом обработки частных репозиториев сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых данных является распространенной причиной ресурсозатрат. При работе над этапом обработки частных репозиториев сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде. Воздерживайтесь от использования обширных скриптов в пользу небольших, тестируемых единиц. При сбое какого-либо шага причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.
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"
Локальная точка входа: поддержание работы «песочницы»
Этот этап с использованием локальной точки входа работает наилучшим образом, когда его рассматривают как измеримую основу. Сначала соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию, прежде чем расширять объем работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия соответствующим элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задач. Установите лимиты на количество токенов за каждый ход и сессию. Инструменты агентов активно расширяют контекст; строгие ограничения предотвращают появление неожиданных счетов.
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
Полная реализация
Полная реализация работает наилучшим образом, если к ней относиться как к измеримой поверхности. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Установите лимит токенов на один ход и на одну сессию. Инструменты агентов активно расширяют контекст; жёсткие ограничения не позволяют демо-версиям превращаться в неожиданные счёты.
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.")
Запуск
Наилучшим образом это работает, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Установите лимиты на количество токенов за ход и за сессию. Инструменты типа агентов активно расширяют контекст; строгие ограничения предотвращают появление неожиданных счетов. Наилучшим образом это работает, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг сбивается, причина должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
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()
Расширение системы
На этапе расширения системы необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывать скрытое состояние. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не допускайте молчаливого частичного выполнения задачи. При следующем шаге, представляющем собой код или вызов инструмента, отдавайте предпочтение структурированным выходным данным с проверкой по шаблону перед текстовыми описаниями в свободной форме.
Аспекты безопасности
На этапе учета аспектов безопасности необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывания скрытого состояния. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. При следующем шаге, представляющем собой код или вызов инструмента, следует отдавать предпочтение структурированным выводам с проверкой схемы перед свободным текстовым описанием.
Заключение
На этапе завершения необходимо определить входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Предпочитайте структурированные выходные данные с проверкой схемы вместо свободного текста, когда следующим шагом является обработка кода или вызов инструмента. На этапе завершения необходимо определить входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага он должен указывать на конкретную причину, а не на сложную структуру обработки данных.
Чек-лист операций
На этапе операционного чек-листа необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии.
Документируйте одновременно «идеальный» сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующими улучшениями.
Предпочитайте структурированные выходные данные с верификацией по схеме вместо свободного текста, когда следующим шагом является работа с кодом или вызов инструмента.
Устанавливайте точки контроля после дорогостоящих шагов. Система возобновления работы не должна повторно взимать плату за тот же вызов большой языковой модели, когда оператор пытается выполнить более поздний элемент.
Фиксируйте версии зависимостей и записывайте хэш изображения, с использованием которого выполнялась демонстрация. Воспроизводимость важнее коллективных знаний.
Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости заранее помогает избежать неожиданных счетов при переходе с демо-среды в общедоступные среды.
Перед тем как переводить решение в более широкое использование, заморозьте версии, сохраните эталонный отчет для критически важных этапов и уточните шаги для отката. В общедоступных средах необходимы ограничения на количество запросов, проверки принадлежности и четко определенный ответственный за обновление секретов. Предпочтите надежность любой сложной одноразовой демонстрации.
Примечание для 0b5d2687b03c: не храните ключи поставщика в репозитории, установите лимит на количество токенов за сессию и сохраняйте отчеты рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.
Для этапа 0 записки по укреплению безопасности необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф.
Подробности укрепления 0/810: измеряйте время выполнения, класс ошибок и расход токенов для данной записки, затем принимайте решение о сохранении изменений на основе фиксированного набора критериев, а не на основе устных замечаний.
При работе над первым этапом записки по укреплению безопасности сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Лучше использовать небольшие, тестируемые модули вместо обширных скриптов. Если какой-то шаг не сработает, причина неудачи должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Подробность укрепления безопасности 1/810: измерьте время выполнения, класс ошибки и расход токенов для данной записки, затем решите, следует ли сохранять изменение, опираясь на заранее определенный набор критериев, а не на устные оценки.
Записка по укреплению безопасности 2 будет работать наилучшим образом, если рассматривать ее как объект с измеримыми показателями. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Записывайте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Подробности усиления безопасности 2/810: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменение на основе фиксированного набора вопросов, а не на основе единичных случаев.
На третьем этапе работы над усилием безопасности определите входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Документируйте как успешный путь выполнения, так и путь восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующими улучшениями.
Подробности усиления безопасности 3/810: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменение на основе фиксированного набора вопросов, а не на основе единичных случаев.
При работе над четвертым этапом инструкций по усилению безопасности сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия соответствующим элементам, определите критерии успешности и не допускайте молчаливого частичного выполнения задач.
Подробности усиления безопасности 4/810: измерьте время выполнения, класс ошибки и расход токенов для данной инструкции, затем решите, следует ли сохранять изменение на основе фиксированного набора критериев, а не на основе единичных примеров.
Инструкция по усилению безопасности 5 будет работать наилучшим образом, если рассматриваться как измеримая поверхность. Соберите один эталонный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, которое операторы могут проверять, не читая весь код.
Подробности усиления безопасности 5/810: измерьте время выполнения операции, класс ошибки и количество потраченных токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.
На этапе 6 процесса усиления безопасности определите входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочтительнее использовать небольшие, проверяемые единицы кода вместо обширных скриптов. При сбое шага он должен указывать на конкретную причину, а не на сложную взаимосвязь компонентов.
Подробности усиления безопасности 6/810: измерьте время выполнения операции, класс ошибки и количество потраченных токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.
При работе над этапом 7 записки по усилению безопасности сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами отметьте время выполнения, стоимость токенов или запросов. Очевидность затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.
Подробности усиления безопасности 7/810: измерьте время выполнения, класс ошибки и расход токенов для данной записки, затем решите, следует ли сохранять изменение на основе фиксированного набора критериев, а не на основе устных оценок.
Записка по усилению безопасности 8 будет работать наилучшим образом, если рассматриваться как измеримая поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.
Подробности усиления безопасности 8/810: измерьте время работы стены, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора вопросов, а не на основе единичных примеров.
Маркер переписывания 1 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните слоты [[CODE_n]] без изменений и избегайте повторения предложений из исходного текста.
Маркер переписывания 2 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните слоты [[CODE_n]] без изменений и избегайте повторения предложений из исходного текста.
Маркер переписывания 3 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните слоты [[CODE_n]] без изменений и избегайте повторения предложений из исходного текста.
Маркер переписывания 4 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните слоты [[CODE_n]] без изменений и избегайте повторения предложений из исходного текста.
Перепишите маркер 5 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните поле [[CODE_n]] без изменений и избегайте повторения предложений исходного текста.
Перепишите маркер 6 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните поле [[CODE_n]] без изменений и избегайте повторения предложений исходного текста.
Перепишите маркер 7 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните поле [[CODE_n]] без изменений и избегайте повторения предложений исходного текста.
Перепишите маркер 8 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните поле [[CODE_n]] без изменений и избегайте повторения предложений исходного текста.
Перепишите маркер 9 для 0b5d2687b03c: перефразируйте сопутствующие утверждения на языке операторов, сохраните поле [[CODE_n]] без изменений и избегайте повторения предложений исходного текста.
Перепишите маркер 10 для 0b5d2687b03c: перефразируйте соседние утверждения на языке операторов, сохраните слоты [[CODE_n]] без изменений и избегайте повторения исходных предложений.