آموزش نصب و اجرای Deer Workflow روی VPS
با استفاده از Bun و systemd، گرافهای عامل Deer Workflow را روی سرور شخصی اجرا کنید. این راهنما نحوه مدیریت متغیرهای PATH و لاگگیری دقیق برای عیبیابی را آموزش میدهد.
آنچه میسازید
Deer Workflow یک runtime مبتنی بر کد برای گرافهای عامل (agent graphs) است: جریان کنترل در یک فایل TypeScript قرار دارد که میتوانید آن را بازبینی کنید و عامل کدنویسی فقط بخشهایی را انجام میدهد که نیاز به قضاوت دارند. این راهنما آن را روی یک VPS با سیستمعامل Ubuntu نصب میکند، یک گراف نمونه را بهصورت headless تحت systemd اجرا میکند و جریان رویدادهای قابلخواندن توسط ماشین را در یک فایل لاگ مینویسد تا بتوانید هنگام شکست خوردن یک اجرا در ساعت 3 صبح، آن را جستجو کنید.
اجزا کوچک هستند. Bun رابط خط فرمان (CLI) را اجرا میکند. یک CLI عامل کدنویسی، مانند Codex یا Claude Code، کارهای مربوط به مدل را انجام میدهد. یک بسته npm با نسخه ثابت (pinned)، runtime را نگه میدارد. یک فایل TypeScript گراف شما را در خود جای میدهد. یک سرویس و تایمر systemd آن را طبق زمانبندی اجرا میکنند. بیشتر مطالب این راهنما به بخشهایی اختصاص دارد که واقعاً دچار مشکل میشوند: متغیر PATH در داخل یک unit فایل systemd، اعتبارنامههای عامل در نشستی که shell ورود ندارد، و ثابت کردن وابستگیای که اولین بار در ژوئیه 2026 منتشر شده است.
سازنده بصری، کد، یا صرفاً پرامپتنویسی برای عامل
یک کاربر self-hoster که قصد دارد کارها را با یک مدل خودکار کند، یکی از سه روش زیر را انتخاب میکند که هر کدام به شکل متفاوتی با شکست مواجه میشوند.
سازنده بصری (Visual builder) یک بوم، کتابخانهای از گرهها و یک رابط کاربری در اختیار شما میگذارد که حتی افراد غیربرنامهنویس هم میتوانند از آن استفاده کنند. این یک مزیت واقعی است و این حوزه آنقدر شلوغ است که بررسی کاملی از جایگزینهای self-hosted برای n8n برای انتخاب وجود دارد. هزینه این روش این است که منطق برنامه در نهایت به یک سند JSON تبدیل میشود که توسط رابط کاربری نوشته شده است. تفاوت (diff) این سند بسیار شلوغ و نامفهوم است، بنابراین بررسی تغییرات به جای خواندن پچ، مستلزم باز کردن بوم طراحی است.
پرامپتنویسی مستقیم برای یک عامل (Agent)، روش دوم است. شما کل کار را در یک پاراگراف توصیف میکنید و به مدل اجازه میدهید ترتیب، تلاشهای مجدد و زمان توقف را تعیین کند. این روش تا روزی که مدل تصمیم متفاوتی بگیرد، کار میکند. هیچ diff وجود ندارد، چون هیچ artifact یا خروجی ثابتی وجود ندارد: طرح در گفتگو زندگی میکرد و حالا آن گفتگو از بین رفته است.
ارکستراسیون در قالب کد، روش سوم است. ترتیب گامها، توزیع کار (fan-out)، تلاشهای مجدد و مدیریت خطا، کدهای معمولی TypeScript در git هستند. مدل فقط در نقاطی فراخوانی میشود که نیاز به قضاوت دارد و نه جای دیگر. هزینه این روش این است که شخصی باید آن کد را بنویسد و نگهداری کند، و همکارانی که TypeScript نمیدانند، نمیتوانند آن را ویرایش کنند.
مزایای استفاده از graph runtime و هزینههای آن
- جریان کنترلی قابل بازبینی. گراف یک فایل است. تغییر در سیاست تلاش مجدد (retry policy) در یک pull request به صورت سه خط تغییریافته نمایش داده میشود، نه به شکل یک جعبه جابهجا شده.
- مدیریت خطا در کنترل نسخه. اتفاقی که هنگام شکست مرحله چهارم رخ میدهد، مکتوب، تستشده و همراه با سایر بخشهای زیرساخت شما برچسبگذاری میشود.
- عاملی (agent) که قابل جایگزینی است. این runtime دارای آداپتورهایی برای Codex، Claude Code و Pi است. تغییر مدل اجراکننده یک مرحله، تنها با یک import انجام میشود.
- اجرایی که میتوانید آن را مشاهده کنید. فازها و رویدادها به صورت دادههای ساختاریافته از runtime خارج میشوند، بنابراین اجرای headless، رکوردی باقی میگذارد که میتوانید آن را جستجو کنید.
تمرین کلی، یعنی طراحی حلقهای که مدل درون آن اجرا میشود بهجای بهبود یک prompt واحد، loop engineering نام دارد و یک graph runtime روشی ملموس برای پیادهسازی آن است. هزینه این کار، راهاندازی آن است: نصب یک runtime، احراز هویت یک CLI برای agent، نبود رابط کاربری برای افراد غیربرنامهنویس و یک وابستگی (dependency) نوپا که باید آن را زیر نظر داشت.
پروژه جدید است، بنابراین نسخه را ثابت (pin) کنید
پروژه Deer Workflow تحت مجوز MIT منتشر شده و جدید است. تا تاریخ 19 August 2026، این مخزن دارای 47 کامیت در main است. در npm سه نسخه منتشر شده وجود دارد: 0.0.1 و 0.1.0 در تاریخ 26 July 2026، و سپس 0.2.0 در تاریخ 27 July 2026. برای هر کدام یک git tag وجود دارد و تغییرات بین آنها در changelog قابل مشاهده است. بخش Unreleased در آن، دستور deer-workflow agent را حذف کرده است، بنابراین main و جدیدترین نسخه منتشر شده دیگر CLI مشابهی ارائه نمیدهند.
این موضوع دلیلی برای اجتناب از پروژه نیست. بلکه دلیلی است برای اینکه یک نسخه دقیق را نصب کنید و بدانید کدام نسخه را نصب کردهاید.
- یک نسخه دقیق را نصب کنید، هرگز از بازه (range) استفاده نکنید.
- آن نسخه را در همان مخزنی که نمودارهای خود را دارید، ثبت کنید.
- پس از هر ارتقا، قبل از اینکه تایمر دوباره آن را اجرا کند، نمودار خود را یک بار بهصورت دستی اجرا کنید.
نصب Bun و runtime عامل (agent)
تمام مراحل زیر باید توسط یک کاربر عادی با دسترسی sudo انجام شود. از اجرای آنها با کاربر root خودداری کنید. CLIهای عامل، اعتبارنامهها را در دایرکتوری home کاربری که وارد سیستم شده ذخیره میکنند و unit مربوط به systemd نیز باید بعداً با همان کاربر اجرا شود تا بتواند به آنها دسترسی داشته باشد.
sudo apt update
sudo apt install -y curl unzip jq git nodejs npm
curl -fsSL https://bun.com/install | bashنصبکننده Bun یک آرشیو zip را استخراج میکند، بنابراین unzip باید از قبل نصب شده باشد. نصبکننده خطوط مربوط به PATH را به پروفایل shell شما اضافه میکند؛ از آنجایی که shell فعلی شما قبلاً آن فایل را خوانده است، یک shell جدید باز کنید یا این دو خط را شخصاً به ~/.bashrc اضافه کرده و آن را reload کنید.
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$HOME/.npm-global/bin:$PATH"bun --versionاین دستور یک شماره نسخه چاپ میکند. خطای bun: command not found به این معنی است که خط PATH در shell فعلی شما وجود ندارد، نه اینکه نصب با شکست مواجه شده باشد. پیش از نصب مجدد هر چیزی، دستور ls ~/.bun/bin را اجرا کنید.
اکنون نوبت به runtime عامل میرسد. Codex CLI به صورت پیشفرض انتخاب شده و از طریق npm نصب میشود. یک prefix در سطح کاربر برای npm تنظیم کنید تا نصب سراسری (global) نیازی به دسترسی root نداشته باشد.
npm config set prefix "$HOME/.npm-global"
npm install -g @openai/codex
command -v codex
codexدستور command -v codex باید مسیری را در زیر $HOME/.npm-global/bin چاپ کند. اجرای دستور codex به تنهایی، CLI را باز میکند که در آنجا باید با حساب کاربری ChatGPT خود وارد شوید. این کار را همین حالا انجام دهید تا بتوانید خروجی صفحه را مشاهده کنید.
Claude Code به عنوان یک runtime جایگزین عمل میکند و نصبکننده اختصاصی خود را دارد.
curl -fsSL https://claude.ai/install.sh | bash
claude --versionنصب موفقیتآمیز، نسخهای مانند 2.1.211 (Claude Code) را چاپ میکند. برای ورود به سیستم، یک بار دستور claude را اجرا کنید. این فرآیند از همان نوع است و همان دسترسی به فایلهای شما را دارد که سایر عاملهای میزبانیشده توسط شما دارند؛ بنابراین نکات مربوط به حساب کاربری و امنسازی در اجرای یک عامل کدنویسی روی VPS در اینجا نیز بدون تغییر صدق میکنند.
نصب Deer Workflow و تعیین نسخه دقیق
bun install --global @deerwork-ai/deer-workflow@0.2.0
command -v deer-workflowcommand -v مسیر مطلق را چاپ میکند که معمولاً /home/<your user>/.bun/bin/deer-workflow است. آن را در جایی یادداشت کنید. فایل unit مربوط به systemd نمیتواند از نام ساده استفاده کند.
نسخه را در دستور نصب حفظ کنید. حذف @0.2.0 باعث میشود هر نسخهای که در روز اجرای دستور جدیدترین است نصب شود؛ این کار در پروژهای با 47 کامیت میتواند باعث تغییر CLI در زمانبندیهایی شود که کسی آنها را نظارت نمیکند.
نمودارها را در یک مخزن git قرار دهید
mkdir -p ~/workflows/logs
cd ~/workflows
git initبرنامه Codex بررسی میکند که آیا در حال اجرا درون یک مخزن git است یا خیر؛ به همین دلیل CodexAgentConfig دارای گزینه skipGitRepositoryCheck برای مواردی است که امکان استفاده از مخزن وجود ندارد. روی VPS شخصی خود، میتوانید یک مخزن در اختیار آن قرار دهید و باید این کار را انجام دهید: یک نمودار در واقع کد است، و اگر کد تحت کنترل نسخه (version control) نباشد، استدلال برای نوشتن ارکستراسیون به عنوان کد (orchestration as code) از بین میرود. دایرکتوری logs را اکنون ایجاد کنید، زیرا systemd آن را برای شما ایجاد نخواهد کرد.
نوشتن یک گراف
یک گردشکار (workflow) یک ماژول معمولی TypeScript است. این ماژول meta را صادر میکند که شیئی شامل نام، توضیحات و لیست مرتبشدهٔ فازهاست، و همچنین هندلر را به عنوان default یا یک export نامگذاریشده به نام run صادر میکند. در داخل هندلر، شما از هلپرهای موجود در پکیج استفاده میکنید. phase() مشخص میکند که اجرا در کدام مرحله قرار دارد، log() یک خط پیشرفت (progress) مینویسد، agent() یک پرامپت به ایجنت کدنویسی ارسال میکند، parallel() لیستی از وظایف را بهصورت همزمان اجرا میکند و pipeline() لیستی از آیتمها را از چندین مرحله عبور میدهد.
این فایل را با نام ~/workflows/log-triage.ts ذخیره کنید.
import { agent, log, parallel, phase } from "@deerwork-ai/deer-workflow";
export const meta = {
name: "log-triage",
description: "Groups recent service errors and writes one short report.",
phases: [{ title: "Collect" }, { title: "Classify" }, { title: "Report" }],
exampleArgs: { service: "nginx", hours: 24 },
};
export default async function workflow(args: { service: string; hours: number }) {
if (!args?.service) throw new Error("input needs a service name");
phase("Collect");
log(`Reading ${args.hours}h of logs for ${args.service}`);
const found = await agent<{ patterns: string[] }>(
`Read the last ${args.hours} hours of journalctl -u ${args.service} and list the distinct error patterns.`,
{
sandbox: "read-only",
schema: {
type: "object",
properties: { patterns: { type: "array", items: { type: "string" } } },
required: ["patterns"],
additionalProperties: false,
},
},
);
phase("Classify");
log(`Classifying ${found.patterns.length} patterns`);
const notes = await parallel(
found.patterns.map((pattern) => () =>
agent(`Explain this error and its most likely cause: ${pattern}`, { sandbox: "read-only" }),
),
);
phase("Report");
return agent(`Write a short operations report from these notes: ${JSON.stringify(notes.filter(Boolean))}`);
}چهار جزئیات در آن فایل اهمیت ویژهای دارند.
schemaدر یک فراخوانیagent()درخواست خروجی ساختاریافته میکند و فراخوانی، شیء پارسشده را برمیگرداند.found.patternsیک آرایه واقعی است که بقیه گراف میتوانند روی آن حلقه بزنند. بدون schema،agent()یک رشته برمیگرداند و شما مجبور به پارس کردن متن هستید.sandboxتعیین میکند که آن مرحله به چه چیزی دسترسی داشته باشد.read-onlyنوشتن را مسدود میکند،workspace-writeاجازه نوشتن محافظتشده را میدهد وdanger-full-accessمحافظ را حذف میکند. این تنظیم برای هر فراخوانی اعمال میشود، بنابراین یک گراف میتواند بهطور گسترده بخواند و فقط در یک نقطه بنویسد.parallel()توابع را میپذیرد، نه promiseها را.map((pattern) => () => agent(...))لیستی از thunkها میسازد تا زمانبندی اجرا (runtime) تصمیم بگیرد هر کدام چه زمانی شروع شوند. ارسال مستقیمagent(...)باعث میشود هر فراخوانی دقیقاً در لحظه ساخته شدن لیست شروع شود.- یک وظیفه شکستخورده در داخل
parallel()بهnullتبدیل میشود و اجرا ادامه مییابد، زیرا تکمیل جزئی طبق طراحی مجاز است. بنابراینnotes.filter(Boolean)صرفاً جنبه تزئینی ندارد: اگر آن را حذف کنید، شاخه شکستخورده متنnullرا در پرامپت مرحله بعدی قرار میدهد.
هلپر ساده agent() از زمانبندی اجرای پیشفرض یعنی Codex استفاده میکند. برای ارسال یک مرحله به Claude Code، کلاس ایجنت را import کرده و مستقیماً آن را فراخوانی کنید.
import { ClaudeAgent } from "@deerwork-ai/deer-workflow";
const claude = new ClaudeAgent({ sandbox: "read-only" });
const summary = await claude.run<string>("Summarise ./report.md in five lines.");این همان چیزی است که یک ایجنت قابلتعویض در عمل نشان میدهد: یک import و یک constructor، در حالی که گراف پیرامون آن بدون تغییر باقی میماند. فلگ --agent codex|claude|pi در CLI متعلق به deer-workflow create است که یک فایل گردشکار را از روی توضیحات تولید میکند. این فلگ تغییری در اینکه deer-workflow run از کدام زمانبندی اجرا استفاده میکند، ایجاد نمیکند.
اجرای دستی و سپس اجرای بدون رابط کاربری (headless)
cd ~/workflows
deer-workflow run ./log-triage.ts --input '{"service":"nginx","hours":24}'در حالت تعاملی، شما یک رابط ترمینال دریافت میکنید: مراحل مربوط به meta در یک سمت و لاگ زنده در سمت دیگر نمایش داده میشود. پیش از آنکه هر چیزی را خودکار کنید، یک دور اجرای کامل را به این روش مشاهده کنید. اگر عامل (agent) وارد نشده باشد یا ورودی شما با امضای handler مطابقت نداشته باشد، بهجای اینکه هفته آینده آن را در یک فایل لاگ پیدا کنید، ظرف چند ثانیه متوجه آن خواهید شد.
برای خودکارسازی، ورودی را به یک فایل منتقل کنید. ~/workflows/input.json را ذخیره کنید:
{ "service": "nginx", "hours": 24 }deer-workflow run ./log-triage.ts --input-file ./input.json --print >> logs/run.jsonl--print، که شکل کوتاه آن -p است، رابط کاربری را غیرفعال کرده و جریان رویدادها را به stdout مینویسد؛ هر شیء JSON در یک خط. در این حالت هیچ چیز دیگری به stdout ارسال نمیشود، بنابراین با append کردن مستقیم به یک فایل .jsonl، فایلی خواهید داشت که تمام خطوط آن قابل تجزیه (parse) هستند.
جریان رویدادها و آنچه باید ساعت 3 بامداد با grep جستجو کرد
هر خط شامل type، sequence، timestamp، workflowId، depth و scriptPath است. نوعها عبارتند از workflow:start، workflow:meta، workflow:end، workflow:error، workflow:phase:start، workflow:phase:end و log. رویدادهای فاز شامل phase، رویدادهای پایان شامل durationMs، یک رویداد log شامل message، و یک رویداد workflow:error شامل error به همراه name، message و معمولاً stack هستند.
این ساختار برای پاسخ به دو سوالی که ساعت 3 بامداد دارید کافی است: آیا عملیات به پایان رسید و در کجا متوقف شد.
grep workflow:error logs/run.jsonl
jq -r 'select(.type == "workflow:error") | .error.message' logs/run.jsonl
jq -r 'select(.type == "workflow:phase:end") | [.phase, .durationMs] | @tsv' logs/run.jsonl
jq -r 'select(.type == "log") | .message' logs/run.jsonlبرای مشاهده اجرای جاری، فایل را دنبال کنید: tail -f logs/run.jsonl | jq -c 'select(.type == "log")'. هر اجرا تعداد کمی خط مینویسد، اما فایل دائماً رشد میکند؛ بنابراین پس از چند هفته اجرای تایمر، یک قانون logrotate برای ~/workflows/logs/*.jsonl اضافه کنید.
اجرای آن تحت systemd
بهجای استفاده از یک daemon که همیشه در حال اجراست، از یک سرویس oneshot به همراه یک timer استفاده کنید. گراف شروع میشود، اجرا میگردد و سپس خاتمه مییابد. فایل /etc/systemd/system/log-triage.service را بنویسید و در آن deploy را با نام کاربری خود جایگزین کنید.
[Unit]
Description=Log triage workflow
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=deploy
WorkingDirectory=/home/deploy/workflows
Environment=HOME=/home/deploy
Environment=PATH=/home/deploy/.bun/bin:/home/deploy/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/deploy/.bun/bin/deer-workflow run ./log-triage.ts --input-file ./input.json --print
StandardOutput=append:/home/deploy/workflows/logs/run.jsonl
StandardError=journal
TimeoutStartSec=3600سپس /etc/systemd/system/log-triage.timer را اجرا کنید:
[Unit]
Description=Run the log triage workflow every night
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl start log-triage.service
systemctl status log-triage.service
sudo systemctl enable --now log-triage.timer
systemctl list-timers log-triage.timerابتدا سرویس را بهصورت دستی اجرا کنید. یک اجرای موفق با غیرفعال شدن واحد (unit) به پایان میرسد و logs/run.jsonl شامل بلوکی از رویدادها خواهد بود که با workflow:end خاتمه مییابند. تنها پس از آن، timer را فعال کنید. دستور list-timers زمان اجرای بعدی برنامهریزیشده را نمایش میدهد و Persistent=true باعث میشود اگر اجرایی به دلیل خاموش بودن سرور از دست رفته باشد، یک بار در زمان بوت بعدی انجام شود. دستور StandardOutput=append: جریان رویدادها را به فایل ارسال میکند و journal را برای سایر موارد آزاد میگذارد تا journalctl -u log-triage.service همچنان خوانا باقی بماند.
چرا گراف در shell من کار میکند اما در systemd با خطا مواجه میشود؟
این چهار مورد را به همین ترتیبی که آمده است بررسی کنید.
واحد (unit) نمیتواند فایلهای اجرایی را پیدا کند. systemd هرگز ~/.bashrc را نمیخواند و PATH پیشفرض آن شامل ~/.bun/bin یا ~/.npm-global/bin نیست. واحد در کمتر از یک ثانیه با خطا مواجه میشود و journalctl -u log-triage.service نشان میدهد که اجرای دستور با شکست مواجه شده است. به همین دلیل است که ExecStart از مسیر مطلق استفاده میکند و چرا Environment=PATH= همچنان هر دو دایرکتوری را فهرست میکند: خودِ runtime باید هنگام شروع یک مرحله از agent، بتواند codex یا claude را پیدا کند.
ایجنت (agent) نمیتواند اعتبارنامههای خود را پیدا کند. CLI ایجنت، اطلاعات ورود را از دایرکتوری home میخواند؛ بنابراین User= و Environment=HOME= را بهطور صریح تنظیم کنید و مسیر homeای که با آن وارد سیستم شدهاید را به آن بدهید. اجرایی که به workflow:start میرسد و سپس workflow:error تولید میکند که پیام آن از سمت CLI ایجنت است (نه از کد خودتان)، تقریباً همیشه به همین دلیل است.
اجرا پس از 90 ثانیه متوقف (kill) میشود. برای Type=oneshot، systemd محدودیت زمانی شروع (start timeout) را برای کل دستور اعمال میکند و مقدار پیشفرض آن 90 ثانیه است. گراف ایجنت ممکن است چندین دقیقه زمان ببرد. ژورنال، Start operation timed out. Terminating. را ثبت میکند، واحد در وضعیت failed قرار میگیرد و فایل لاگ حاوی نیمی از اجرا بدون workflow:end خواهد بود. TimeoutStartSec=3600 به آن یک ساعت زمان میدهد. اگر ترجیح میدهید که اجرا هرگز بر اساس زمان متوقف نشود، از infinity استفاده کنید.
مسیرهای نسبی در جای دیگری حل میشوند. ./log-triage.ts و ./input.json نسبت به WorkingDirectory سنجیده میشوند. اگر آن خط را حذف کنید، systemd فرآیند را در / شروع میکند، جایی که هیچکدام از آن فایلها وجود ندارند.
آنچه ارکستراتور مجاز به انجام آن است
ارکستراتوری که گامهای عامل (agent) را طبق زمانبندی اجرا میکند، فرآیندی است که بدون نظارت مستقیم شما روی سرور اجرا میشود. دو کنترل و یک بودجه در اینجا اهمیت دارند.
کنترل اول، محیط ایزوله (sandbox) برای هر فراخوانی agent() است. گزینه read-only تنظیم پیشفرض مناسب برای هر گامی است که فقط عملیات خواندن انجام میدهد: مانند خواندن لاگها، متریکها یا مخزنی که در حال خلاصهسازی آن هستید. تنها زمانی که گام مورد نظر واقعاً نیاز به نوشتن دارد، آن را به workspace-write تغییر دهید و با استفاده از additionalWritableDirectories بهجای دسترسی به danger-full-access، محدوده قابل نوشتن را کوچک نگه دارید.
کنترل دوم، یک انسان است. برخی گامها هرگز نباید بدون نظارت اجرا شوند: ارسال ایمیل، انتقال پول، حذف دادهها یا تغییر پیکربندی محیط عملیاتی (production). در یک گراف مبتنی بر کد، ایجاد این دروازه آسان است، زیرا هر گام یک خط کد است. اجرای فرآیند را متوقف کنید، اقدام پیشنهادی را ثبت کنید، منتظر پاسخ انسانی بمانید و سپس ادامه دهید. قرار دادن دروازه تأیید پیش از اقدامات عامل این الگو را بهطور کامل پوشش میدهد و باید در هر گرافی که توسط زمانسنج (timer) شروع میشود، گنجانده شود.
بودجه، همان هزینه مالی است. هر فراخوانی agent() یک نشست کامل عامل است و parallel() چندین نشست را بهطور همزمان آغاز میکند؛ بنابراین گرافی که به 12 شاخه تقسیم میشود، هر شب 12 نشست را اجرا میکند، فارغ از اینکه کسی گزارش آن را بخواند یا خیر. اندازهگیریها و محدودیتهای ذکر شده در کنترل هزینههای عامل هوش مصنوعی روی VPS مستقیماً برای یک گراف زمانبندیشده کاربرد دارند.
پیش از ارتقای محیط اجرا (runtime)، فایل تغییرات (changelog) را بخوانید، نسخه دقیق جدید را نصب کنید و گراف خود را یکبار بهصورت دستی با --print اجرا کنید. در پروژهای به این جوانی، سطح دستورات CLI همچنان در حال تغییر است: بخش Unreleased دستوری را حذف کرده که در نسخه 0.2.0 وجود داشت. یک گراف تحت زمانبندی، تنها به اندازه نسخهای که ثابت کردهاید (pin) و آخرین اجرایی که شخصاً مشاهده کردهاید، قابلاطمینان است.
FAQ
آیا به Bun نیاز دارم یا Node.js هم Deer Workflow را اجرا میکند؟
Bun را نصب کنید. بستهٔ منتشرشده، باینری deer-workflow خود را به src/cli.ts که یک فایل منبع TypeScript است ارجاع میدهد و مستندات نیز Bun را بهعنوان پیشنیاز ذکر کردهاند. Bun فایلهای TypeScript را مستقیماً اجرا میکند، بنابراین نیازی به مرحلهٔ build نیست. آن را با دستور sudo apt install -y unzip و سپس curl -fsSL https://bun.com/install | bash نصب کنید و در نهایت با bun --version تأیید نمایید. اگر Codex CLI را از طریق npm نصب میکنید، همچنان به Node.js و npm بهصورت جداگانه نیاز خواهید داشت.
چرا workflow من در ترمینال اجرا میشود اما در systemd شکست میخورد؟
این مشکل تقریباً همیشه به PATH، HOME یا timeout شروع مربوط است. systemd فایل profile شل شما را نمیخواند، بنابراین ExecStart به مسیر مطلق deer-workflow نیاز دارد و Environment=PATH= باید دایرکتوری حاوی codex یا claude را در بر داشته باشد. CLI عامل (agent) اعتبارنامههای خود را از $HOME میخواند، پس User= و Environment=HOME= را روی حسابی که با آن وارد شدهاید تنظیم کنید. همچنین Type=oneshot بهصورت پیشفرض timeout شروع 90 ثانیهای دارد که باعث میشود اجرای عامل در میانهٔ راه متوقف شود و پیام Start operation timed out. Terminating. در journal باقی بماند؛ بنابراین TimeoutStartSec=3600 را تنظیم کنید.
چگونه برای یک مرحله از Claude Code بهجای Codex استفاده کنم؟
هلپر سادهٔ agent() از runtime پیشفرض یعنی Codex استفاده میکند. ClaudeAgent را از بسته وارد (import) کنید، آن را بسازید و برای مراحلی که میخواهید Claude Code مدیریت کند، .run() را فراخوانی کنید. فلگ --agent codex|claude|pi متعلق به deer-workflow create است، یعنی دستوری که یک فایل workflow را از روی توضیحات تولید میکند و تأثیری بر deer-workflow run ندارد. هر عاملی که استفاده میکنید، باید CLI مخصوص به خود را نصب داشته باشد و با همان کاربری که سرویس با آن اجرا میشود، لاگین کرده باشد.
کدام نسخه از Deer Workflow را باید نصب کنم؟
دقیقاً همان نسخهای که تست کردهاید. تا تاریخ 19 August 2026، جدیدترین نسخهٔ منتشرشده 0.2.0 است که در 27 July 2026 عرضه شده و مخزن (repository) شامل 47 کامیت است. مقدار @0.2.0 یا هر نسخهای که در زمان خواندن این متن جدید است را در دستور نصب بنویسید، آن شماره را در git کنار نمودارهای خود نگه دارید و پس از هر بار ارتقا، پیش از آنکه تایمر دوباره آن را اجرا کند، یک نمودار را بهصورت دستی اجرا کنید.