Практычныя нататкі: агент для кода ў прыватным воблаку — OpenCode і лакальная LLM
Практычны разбор тэмы «агент для кода ў прыватным воблаку на OpenCode і лакальнай LLM»: кантракты, праверкі і гатовыя фрагменты кода для каманд, якія ўкараняюць гэты падыход.
Крыніцы: Building a Private Cloud Coding Agent: OpenCode + a Local LLM, Running on Serverless GPUs
Наступныя прыміткі паказваюць практычны падход да стварэння «Агента для кодавання ў прыватным воблака: OpenCode + локальны LLM, які працюе на сервернасаблівых GPU». Акцэнт ставяцца на контракты, перакананняя тэстаў і месца для коду, які можна легка заменіць, а не на мотывацыйныя аспекты. Калі працуеце на этапы адгляду, спачатку запісайце контракт: неабяжныя вхідныя даны, сігнал успеху і тое, што выканаецца у разе частковага невыпання. Такі спіс перакананняя дапамагае заліцваты пазнейшыя змены ў кодзе. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, невыпанне должна паказваць на адну конкрэтную адпаведальнасць, а не на заплутаны працэс.
Чаму самэ гэтая архітэктура
Чаму такая архітэктура працюе найкрашча, калі яе розглядаць як вімерную паверхню. Зафіксавайце адзін ідэальны прыклад, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць масштабы. Разглядзайце гэты этап як кантракт межа вхіднымі данымі і паверынутымі выходнымі рэзультатамі. Даўце назвы артыфактам, задаць крытэрыя успеху і не падзеўляйцеся частым, некомплектным выкананнем задач. Задаць ліміт токенав на кожны раунд і на кожную сесію. Інструменты-агенты агрэсывна расшырваюць кантэкст; строгі ліміты не дазволяюць дэмам ператварыцца на неспакоўныя рахункі.
Блакітнія камені
Элементы лепкія будуўлі працуюць наякша, калі іх розглядаць як вімерную паверхню. Запісаўце адна «золатая» транскрыпцыю, адын прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць масштабы. Запісвайце часы выканання і вартасць токенав або запытаў па боку ад функцыйнальных рэзультатаў. Візуабельнасць вартасцей з самага пачатку запобегае неспакою, калі працэс пераходзіць з дэмавайшага рэжыма ў спяльныя сераўысы.
Modal
Modal работае наяўней, калі яго спрыяваць як мерыемую паверхню. Зберагуце адна ідэальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць сферу дзеяння. Храніце настройкі парадульна коду прыемлівання. Файлы сераўнавальной асяроддзя, хранільнікі секрэтных дадзеных і флагі функцый должны знаходзіцца ў аднам месцы, куда аператары можуць аўдытаваць іх без неабяжнага чытання всіх дадзеных. Задаце ліміты токенаў на кожны рунд і на кожную сесію. Інструменты з агентным падходам агрэсіўна расшырваюць кантэкст; жорсткія ліміты запобегаюць таму, каб дэманстрацыі ператварыліся на неспакоючыя рахункі. Modal работае наяўней, калі яго спрыяваць як мерыемую паверхню. Зберагуце адна ідэальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расшырваць сферу дзеяння. Валідзіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцюг дзеяння.
Ollama
Для этапы Ollama неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы схованы стан. Спрыяйце цій этапы як даговору межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даўце назвы артыфактам, узначыце перакананні на успех і адмовіцеся ад тыхняга частковага завершэння без паведамлення. Аддзельце стварэнне кліента ад цыклу паведамленняў, каб было можна змяніць прадаўцоў без перапісвання машыны стану размовы.
OpenCode
Для этапы OpenCode неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыры завершэння пры змяне коду. Аперацыйныя працавнікі должны магчымае перадзеяць крок з вядомай точкі контролю, не падозрываючы схованы стан. Запісваць час выканання і кост токена або запыту праза функцыйнальных рэзультатаў. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя асяроддзя. Калі наступны крок — гэта код або вызов інструмента, лепш выкарыстоўваць структураваныя выходныя даны з перакананнем схэмы, чым вольныя тэкстовыя апісанні.
Тунель
Для этапы «Тунель» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі павінны магчымае перзапускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Конфігурацыю трэба захаваць пазначыльна ад коду прыкладнення. Файлы сераўіса, хранальнікі секрэтных даных і флагі функцыйяў павінны знаходзіцца ў адном месцы, якое працавнікі можуць пераглядаць, не чытаючы весь ланцуг задач. Калі наступны крок — гэта код або вызов інструмента, лепш выкарыстоўваць структураваныя выходныя даны з пераканальваннем схэмы, чым вольныя тэкстовыя апісанні. Для этапы «Тунель» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі павінны магчымае перзапускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Лепш выкарыстоўваць маленькія, тэставаныя елементы, чым велікія скрыпты. Калі крок не выйшоў, прычына неудачы павінна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг задач.
Прыямельныя умовы і наладка
Калі вы працюеце над праблемамі i наладкай, спачатку запісаце кантракт: неабяжлівыя даныя, сігнал успеху i тое, што выходзіць у разе частковага невыпання. Такі спіс перакладзець пазнейшыя змены коду ў правільны направленні. Спрэцьвуйце да гэтага этапу як да кантракта межа данымі i перакананымі рэзультатамі. Даце назвы элементам, задаце крытэрыі успеху i не падтрымвайце часткова завершэння без паведамлення. Зберагачвайце стабільныя інструкцыі сістэмы i схемы інструментаў. Перадача тых самых прамэры ёсць частым выклікам затрачання рэсурсаў.
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 дэйсна не будзе выдаваць даныя, а толькі працюе
Анкетаванне на 2-му кроцы, якое трывае да пэўнага этапу, найэфектывней працюе, калі яго розглядаць як вімерную характэрыстыку. Запісайце адна «золатая» транскрыпцыю, адзін прыклад неудачы і прыметку па поверненню да пярвоначальнага стану пры розшырэнні масштаба. Запісвайце часы выканання і кост токеноў або запытаў праза функцыйнае рэзультат. Відразлівае паказанне костаў з’являецца прычыной таго, што не будзе неспакойных рахункоў, калі працэс пераходзіць з дэмаверсіі ў спяльныя сераўы. Задаце бюджэт токеноў на кожны раунд і на кожную сесію. Інструменты-агенты агрэсывна расшырваюць кантэкст; строгі ліміты не дазволяюць дэмаверсіям ператварыцца на неспакойныя рахункі.
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()
Пашырэнне сістэмы
У стадыі расшырэння сістэмы неабяжна практычна вказаць інпуты, адміністратара крока і крэтырыя выходу пры зміне коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Спрыяйце цій этапы як даговору межа інпутамі і падтвердзенымы выходам. Дайце назвы артыфактам, практычна вказаць пераканальнія крэтырыя і адмовіцеся ад бяспрамовнага частковага завершэння. Калі наступны крок — гэта код або вызов інструмента, валідавайце структураваныя выходы за дапамою схемы, а не працавайце з вольным тэкстамі.
Аспекты безпекі
У стадыі аналізу пытанняя безпекі трэба з’явіць вхідныя даны, адміністратара крока і крэтырыя для завершэння пры зміне коду. Аперацыйныя працавнікі должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы схованы стан. Запісваць час выканання і кост токена або запытку разам з функцыйнальнымі рэзултатамі. Відразувыя даны пра косцы запобегаюць неспакоўным рахункам, калі працэс пераходзіць з дэмовай асяроддзя ў спакульную.
Вывад
Для этапы Заканчэння неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі павінны магчымае перазапускаць крок з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Конфігурацыю трэба залічыць параду ад коду прыкладнення. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцыйяў павінны знаходзіцца ў аднам месцы, якое працавнікі можуць пераглядаць, не чытаючы весь ланцуг аперацый. Калі наступны крок — гэта код чыя-небудзь вызов інструмента, лепш выкарыстоўваць структураваныя выходныя даны з перакананнем схэмы, чым вольныя тэкстовыя апісанні. Для этапы Заканчэння неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры зміне коду. Аперацыйныя працавнікі павінны магчымае перазапускаць крок з вядомай точкі контролю, не спрабоўваючы здагадвацца пра схованы стан. Лепш выкарыстоўваць маленькія, тэставаныя елементы, чым велікія скрыпты. Калі крок не выйшоў, прычына нехарактэрыстыкі павінна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг аперацый.
Чэк-ліст для аператыўнай працы
У стадыі перагляду канцэларыі аперацыйяў неабходна практычна вызначыць даныя, адпаведальную особу за кожны крок і крэтынія завершэння пры змены коду. Аперацыйныя працавнікі должны магчымае запускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан.
Неабходна адначасова задокументаваць шлях успеху і шлях вярнення да нормальнага стану. Перапрыбуткі, людзкія контралі і обработка некорэктных паведамленняў ёсць часткай продукту, а не чымсь, што дадаецца пазней.
Калі наступны крок — гэта код або вызов інструмента, лепш выкарыстоўваць структураваныя выходны даныя з пераканальванням схемы, чым вольнай формы тэкст.
Заставляйце адбывацца контрольныя пункты пасля дорогіх крокаў. Сістэма вярнення не должна занова ставіць плату за той самы вызов LLM, калі працавнік перапрыбуе пазнейшы вузел.
Фіксуйце версіі залежнасцяў і запішыце хэш адобраза, які выканаў дэманстрацыю. Возможнасць перадарабаткі важлівейшая, чым традыцыйныя знання.
Запісвайце часы выканання аперацый і косць токена чыста запітоў праза функцыйнальныя рэзултаты. Відразлівая візуабільнасць косца з самага пачатку запобегае неспакоўным рахункам, калі парадок пераходзіць з дэмовай асяроддзя ў спадзеленыя асяроддзя.
Перш чым пераводзіць стэк у болей серйозны рэжым, заморожвайце версіі, зафіксавайце «золаты» транскрыпты для крытычных парадакоў і паверкайце крокі для атрыбуцыі. Спадзеленыя асяроддзя выклекваюць патрэбу ў лімітах частоты выканання, пераказах прыналежнасці і чысткай адпаведальнасці за змяну секрэтных даных. Валіце надзейнасць працы над крэатыўнымі, адзінразовымі дэмамі.
Прыметка для 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]] недзейнасцю, і утрымайцеся ад павторэння рэчы з выхіднага тэксту.