Практичні нотатки: створення агента для кодування в приватній хмарі: OpenCode + локальний LLM
Покрокова інструкція з практичних нотаток: створення агента для кодування в приватній хмарі: OpenCode + локальний LLM: контракти, перевірки та слоти для коду для команд, які використовують цю схему.
Джерела: Building a Private Cloud Coding Agent: OpenCode + a Local LLM, Running on Serverless GPUs
Наведені нижче примітки описують практичний підхід до створення агента для кодування в приватній хмарі: OpenCode + локальний LLM, що працює на серверніс-ГПУ. Основна увага приділяється контрактам, перевіркам та шаблонам коду замість мотиваційних формулювань. Під час роботи на етапі огляду спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та наслідки часткової невдачі. Такий перелік допоможе зберегти чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Якщо якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на складну ієрархію операцій.
Чому саме така архітектура
Чому ця архітектура працює найкраще, коли її розглядати як вимірювану поверхню. Запишіть один ідеальний приклад роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу роботи. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Встановіть ліміти на кількість операцій за раз та за сеанс. Інструменти типу агентів активно розширюють контекст; жорсткі обмеження запобігають тому, щоб демонстрації перетворювалися на несподівані рахунки.
Будівельні блоки
Блоки будівництва працюють найкраще, коли їх розглядати як вимірювану поверхню. Запишіть один ідеальний приклад роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу завдань. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ. Визначте бюджет на токени на кожен хід та сеанс. Інструменти типу агентів активно розширюють контекст; жорсткі обмеження не дозволяють демо-версіям перетворюватися на несподівані рахунки.
Modal
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
Під час роботи над етапом оркестрації через скрипт запуску спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Зберігайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Записуйте ідентифікатор запиту, ідентифікатор моделі та час відгуку при кожному виклику. Без цих даних періодичні помилки постачальника виглядають як баги додатку. Під час роботи над етапом оркестрації через скрипт запуску спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність подальших змін у коді. Віддавайте перевагу малим, тестованим одиницям коду перед величезними скриптами. Коли якась крок виявляється невдалим, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій.
Крок 1 — Запустіть Ollama у фоновому режимі та перевірте, чи процес не зупинився відразу
Етап Крок 1 «Запуск Ollama» найкраще працює, якщо його розглядати як показник для оцінки. Збережіть один приклад успішної роботи, один випадок збою та примітки щодо скасування змін перед розширенням обсягу завдань. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Позначте всі необхідні елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання завдань. Забезпечте фіксацію версії інтерпретатора та файлу з параметрами залежностей перед початком виконання циклу. Відмінності між версіями програми на ноутбуці та в середовищі 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 «Pull the stage» працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Встановіть ліміти на кількість токенів за хід та за сеанс. Інструменти типу агентів активно розширюють контекст; жорсткі обмеження запобігають тому, що демонстрації перетворюються на несподівані рахунки.
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 «Pull the stage» працює найкраще, коли його розглядають як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу малим, тестованим одиницям перед величезними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність дій.
Етап 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
На етапі Step 5 Write OpenCode необхідно визначити вхідні дані, відповідальну особу за виконання кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке відображення витрат запобігає несподіваним рахункам під час переходу з демо-середовища у спільні середовища. У разі, коли наступним кроком є написання коду або виклик інструменту, краще використовувати структуровані результати з перевіркою схеми, ніж вільний текст.
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 у першому плані
Для етапу Step 6 Start OpenCode необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Конфігурацію слід зберігати окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функціоналу мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Коли наступним кроком є написання коду чи виклик інструменту, краще використовувати структуровані результати з перевіркою схеми, ніж вільний текст.
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-го етапу запису про посилення безпеки визначте вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не здогадуючись про прихований стан. Документуйте як успішний, так і відновлювальний сценарії. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації.
Деталь посилення безпеки 3/810: виміряйте час виконання, клас помилки та кількість витрачених токенів для цього запису, а потім вирішіть, чи залишити зміни, ґрунтуючись на фіксованому наборі питань, а не на окремих випадках.
Під час виконання 4-го етапу процедури зміцнення спочатку запишіть угоду: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як угоду між вхідними даними та перевіреними результатами. Дайте назви елементам, визначте критерії успіху та не допускайте беззвучного часткового виконання завдань.
Деталь зміцнення 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]] недоторканим та уникайте повторення речень з вихідного тексту.