Phiên bản: 0.20.0 · Cập nhật: 2026-10-14
web_search chạy nhanh hơn — không có gì thêm thì vẫn tìm được qua DuckDuckGo.git clone <repo> my-agent-crew && cd my-agent-crew
uv sync
export OPENROUTER_API_KEY=… # hoặc đặt sau trong web: Quản lý → Kết nối, xem §4
uv run python -m my_agent_crew # http://127.0.0.1:8765
Lần chạy đầu tạo home ~/.my-agent-crew/ với config.yaml, agent.sqlite3, agent.yaml cho master và users/owner/. Mở trình duyệt, gõ một câu, xem run xuất hiện ở panel hoạt động.
Thử không tốn tiền: MY_AGENT_ROUTES=fake:echo uv run python -m my_agent_crew; gõ /tool workspace_list để thấy vòng lặp tool chạy qua provider giả. Đổi thành MY_AGENT_ROUTES=fake:slow thì cùng provider giả ấy chờ 0,1 giây giữa hai mảnh, để nhìn câu trả lời và đối số của lời gọi tool tới dần. Máy đang có một crew chạy trên home mặc định thì lệnh này dừng ngay với lời báo home đang có một server khác chạy (xem §3): cho lần thử một home và một cổng riêng, ví dụ MY_AGENT_HOME=/tmp/crew-thu MY_AGENT_ROUTES=fake:echo uv run python -m my_agent_crew --port 8799.
~/.my-agent-crew/
├── config.yaml # routes, cost_cap_usd, max_steps, language, timezone, web_url, autonomous_default, approval_ttl_seconds, mcp_servers, browser_*, desktop_socket
├── env # KEY=value, chmod 600, KHÔNG nằm trong repo; token đăng nhập MCP cũng ghi ở đây
├── agent.yaml # master
├── agent.sqlite3
├── users/owner/ # USER.md + facts/
├── skills/ # skill dùng chung
├── .agents/ # kit dùng chung (commands, agents, skills, settings.json)
├── workspace/inbox/ # ảnh từ Telegram
├── browser/<id>/ # hồ sơ trình duyệt của từng agent (cookie, đăng nhập), xem §4b
├── desktop/ # socket của trình trợ giúp desktop (sock), xem §4c
└── agents/<id>/ # mỗi agent một thư mục, xem agents.md
Đổi home bằng MY_AGENT_HOME. Mỗi lần thử nghiệm nên trỏ MY_AGENT_HOME sang thư mục tạm thay vì đụng home thật.
Một home, một server. Server giữ home của nó từ lúc khởi động tới khi tiến trình mất, dù tiến trình kết thúc thế nào. Lần khởi động thứ hai trên cùng home, dù ở cổng khác hay với --no-schedule, không đọc gì của home, nói home đang có một server khác chạy rồi thoát với mã 1. Trước đây một lần khởi động như vậy đóng các run của server đang chạy trong sổ rồi mới hỏng vì cổng đã bị chiếm. Các lệnh con agent không giữ home nên vẫn dùng được khi server đang lên. Home nằm trên hệ tệp không cho khoá thư mục thì server không khởi động và nói đúng lỗi của hệ thống.
config.yaml chỉ nhận những khoá nó biết; một khoá lạ làm server dừng lúc khởi động. Máy chủ MCP khai dưới mcp_servers không mang bí mật nào: giá trị header lấy từ biến môi trường, còn đăng nhập OAuth từ thẻ Kết nối ghi token vào env dưới tên MCP_<MÁY_CHỦ>_ACCESS_TOKEN và MCP_<MÁY_CHỦ>_REFRESH_TOKEN, kèm nơi đã cấp chúng (MCP_<MÁY_CHỦ>_ISSUER) và tên của crew ở nơi đó (MCP_<MÁY_CHỦ>_CLIENT_ID) (tools.md).
| Biến | Ý nghĩa |
|---|---|
MY_AGENT_HOME |
thư mục home |
MY_AGENT_ROUTES |
danh sách model, ví dụ openrouter:a,openrouter:b hoặc fake:echo |
MY_AGENT_COST_CAP_USD, MY_AGENT_MAX_STEPS, MY_AGENT_AUTONOMOUS, MY_AGENT_APPROVAL_TTL_SECONDS, MY_AGENT_TIMEZONE |
ghi đè config.yaml |
MY_AGENT_WEB_URL |
ghi đè web_url của config.yaml: địa chỉ người dùng mở web, ví dụ http://127.0.0.1:8765, để tin gửi ra kênh viết được link về web. Chỉ nhận http/https, host, cổng và path trơn; có tên đăng nhập, query hay fragment có nội dung, khoảng trắng nằm giữa địa chỉ hay ký tự ẩn ở bất cứ đâu thì server dừng lúc khởi động, và câu lỗi không lặp lại giá trị. Khoảng trắng ở hai đầu, dấu / cuối và một dấu ? hay # không có gì theo sau thì được bỏ đi, không làm server dừng. Host chỉ gồm chữ không dấu, số, ., -, _ hoặc là địa chỉ IPv6 trong ngoặc vuông, nên tên miền có chữ ngoài ASCII phải viết ở dạng punycode (xn--…). Để trống thì không link nào được viết |
OPENROUTER_API_KEY |
bắt buộc trừ khi dùng fake: |
TAVILY_API_KEY / BRAVE_API_KEY |
nguồn tìm kiếm trả phí cho web_search (không có vẫn chạy bằng DuckDuckGo) |
FIRECRAWL_BASE_URL |
host firecrawl, ví dụ http://127.0.0.1:3002; bật tìm kiếm + scrape markdown |
FIRECRAWL_API_KEY |
chỉ cần cho firecrawl cloud; host tự dựng để trống |
tên do telegram.token_env trong agent.yaml chỉ định |
token bot |
Server tự nạp <home>/env lúc khởi động; biến đã có sẵn trong môi trường tiến trình thắng giá trị trong tệp.
Quản lý từ web. Quản lý → Kết nối liệt kê các khoá theo việc chúng phục vụ (mô hình, tìm kiếm, Telegram, biến khác): đã đặt hay chưa, đặt từ tệp hay từ môi trường tiến trình. Đặt/Thay ghi vào <home>/env (chmod 600, giá trị bọc nháy đơn nên source an toàn, không nhận xuống dòng) rồi dựng lại provider, nguồn tìm kiếm và kênh Telegram ngay — không cần khởi động lại. Kiểm tra gọi thử OpenRouter, Telegram getMe, Ollama, Firecrawl, Tavily (/usage, miễn phí, kèm số lượt đã dùng) và Brave (một lượt tìm 1 kết quả — nút ghi rõ “tốn 1 lượt tìm”); hết lượt (429) được báo khác với khoá sai. Xoá chỉ gỡ được giá trị nằm trong tệp; giá trị do môi trường tiến trình cấp phải gỡ ở nơi khởi động server. Trước khi ghi, server dựng thử cả đội với giá trị mới; nếu đội không chạy được (vd. xoá khoá duy nhất mà route cần) thì từ chối (409) và tệp giữ nguyên. Bot Telegram chỉ dựng lại khi token hay chat_id của nó đổi; đổi khoá khác không cắt tin đang trả lời. Giá trị chỉ đi một chiều: API (/api/credentials) không bao giờ trả khoá bí mật về, chỉ trả địa chỉ host (mật khẩu trong URL bị che).
Tuyến mô hình chung (tuyến agent không có tuyến riêng dùng, và tuyến agent có tuyến riêng quay về khi không tuyến nào của nó có khoá) sửa ở thẻ Tuyến mô hình cùng trang: thêm/bớt/sửa dòng rồi Lưu tuyến. Server từ chối (409) nhà cung cấp chưa có khoá, danh sách đội không chạy được, và fake (chỉ lặp lại tin nhắn) khi tuyến đang lưu chưa dùng nó, rồi mới ghi routes vào <home>/config.yaml kiểu round-trip (giữ chú thích và khoá khác) và áp dụng từ lượt kế tiếp. /api/connections trả routes_source: env khi MY_AGENT_ROUTES đang đặt — biến thắng tệp nên trang chỉ cho xem —, config hoặc default. Xoá khoá mà tuyến duy nhất cần bị từ chối kèm tên tuyến dạng provider:model và cách gỡ (thêm tuyến khác trước).
Chỉ nhận request cục bộ. Mọi đường dẫn (API lẫn trang) từ chối (403) request có Host không phải localhost hay địa chỉ IP, hoặc Origin khác đúng host:port đang gọi (kể cả trang local ở cổng khác) — để trang web lạ trỏ tên miền về 127.0.0.1 (DNS rebinding) không đọc hay sửa được đội. Mở UI qua tên máy (vd. Tailscale MagicDNS) thì thêm tên đó vào MY_AGENT_ALLOWED_HOSTS (phân cách dấu phẩy) trong môi trường khởi động server; truy cập bằng IP không cần. Trang 403 nêu đúng tên host bị từ chối và biến cần thêm, log server ghi tên đó một lần. web_url đi cùng hàng rào này: nếu nó dùng một tên máy thì tên đó cũng phải có trong MY_AGENT_ALLOWED_HOSTS, không thì link trong tin gửi ra kênh mở ra trang 403. Server không đối chiếu hai giá trị lúc khởi động, nên hãy bấm thử một link sau khi đặt.
Không có đăng nhập. Ai tới được cổng là điều khiển được đội: đọc hội thoại, sửa agent, đặt khoá, và POST /api/inbound chạy một lượt không cần xác thực (header Origin không có ở curl nên hàng rào Origin không chặn). Hàng rào Host chỉ chống trang web lạ trong trình duyệt, không phải kiểm soát truy cập. Vì vậy chỉ mở cổng trên máy hoặc trong tailnet riêng; đừng đưa ra ngoài bằng Tailscale Funnel, ngrok, Cloudflare Tunnel hay reverse proxy công khai — tên của đường hầm công khai cũng đừng thêm vào MY_AGENT_ALLOWED_HOSTS.
Quy tắc: bí mật chỉ nằm trong tệp env ngoài repo; agent.yaml chỉ ghi tên biến (token_env: TELEGRAM_BOT_TOKEN), không ghi giá trị. Log server đi qua bộ lọc redact nên token không xuất hiện trong logs/.
Mặc định không agent nào có trình duyệt. Bật nó là ba việc, theo thứ tự này:
uv run playwright install chromium # tải bản Chromium mà gói Playwright ghim; Linux thêm --with-deps
~/Library/Caches/ms-playwright trên macOS, ~/.cache/ms-playwright trên Linux, hoặc nơi PLAYWRIGHT_BROWSERS_PATH chỉ), không nằm trong repo hay home. Muốn dùng Chrome đã cài trên máy thì bỏ bước này và ghi browser_channel: chrome.browser_enabled: true vào <home>/config.yaml (hoặc đặt MY_AGENT_BROWSER_ENABLED=1), rồi khởi động lại. Server xem trình duyệt đã cài hay chưa một lần, lúc khởi động, nên cài xong cũng phải khởi động lại. Bật mà còn thiếu thì server vẫn lên, ghi một dòng cảnh báo nêu thứ thiếu và lệnh cài, và không agent nào được trao tool trình duyệt.browser: read (mở, đọc và in trang) hoặc browser: act (thêm bấm, gõ và tải tệp về) vào agent.yaml của agent, hoặc chọn ở trình sửa agent trên web; browser_hosts là những host nó tới mà không ai bị hỏi (agents.md). Sửa từ web không cần khởi động lại.Quản lý → Kết nối có thẻ “Trình duyệt” nói máy đang ở đâu trong ba bước đó: đã bật hay chưa, kênh và phiên bản, hoặc thứ còn thiếu kèm lệnh cài, và agent nào đang có trình duyệt chạy. Thẻ này chỉ để xem: công tắc của máy nằm trong config.yaml. Mọi khoá browser_* của máy ở agents.md, còn tool làm gì và khi nào hỏi người ở tools.md.
Hồ sơ là dữ liệu đăng nhập. Mỗi agent có một hồ sơ riêng ở <home>/browser/<agent id>/ (thư mục chỉ chủ đọc được), giữ cookie và phiên đăng nhập qua các lần khởi động lại. Trước khi bật cho một agent, làm ba việc:
browser/ vào .gitignore của home. Server không viết tệp đó hộ.<home>/browser, và lệnh shell_run chạy trong sandbox cũng vậy. Nhưng shell chỉ có sandbox khi agent ghi shell_network: false hoặc có shell_write_paths; một agent có shell_run không sandbox thì đọc được hồ sơ của mọi agent bằng một lệnh cat. Agent giữ trình duyệt nên có shell trong sandbox, hoặc không có shell_run (danh sách tools: không nêu nó). Shell trong sandbox cũng không kết nối được tới cổng --port của chính server, nên không gọi API của đội để đổi hồ sơ hay dừng trình duyệt của agent nào. Máy chủ MCP và job kiểu command chạy ngoài hàng rào này.browser_screenshot lưu ảnh vào screenshots/,
còn browser_download và browser_pdf lưu tệp vào downloads/, trong workspace của agent,
hoặc trong thư mục đầu tiên của write_paths khi agent có danh sách đó. Ảnh, hoá đơn hay
bản in của một trang đã đăng nhập là dữ liệu riêng: workspace là một repo git thì cho hai thư
mục ấy vào .gitignore của repo. Server không tự dọn downloads/: khi các tệp nằm thẳng
trong nó đã chiếm tới 1 GB thì hai tool từ chối lưu thêm cho tới khi có người dọn bớt.Trong hồ sơ còn có crew-logins.json (chế độ 600), nơi server ghi host và hạn của các cookie
đăng nhập mỗi lần một trình duyệt trên hồ sơ ấy đóng, để thẻ trên trang agent kể lại được khi
trình duyệt không chạy; tệp không có tên hay giá trị cookie nào.
Bản sao lưu cả home mang theo các phiên đăng nhập ấy; giữ nó như giữ tệp env. Gỡ một agent khỏi đội (web hoặc DELETE /api/agents/{id}) xoá luôn hồ sơ trình duyệt của nó. Tắt browser_enabled thì các hồ sơ vẫn nằm nguyên và vẫn đóng với mọi tool; muốn đăng xuất hẳn thì dừng server rồi xoá <home>/browser/<agent id>/.
Trình duyệt chạy ẩn (headless), mỗi agent một tiến trình Chromium, mở ở lời gọi đầu tiên và tự dừng sau browser_idle_minutes phút không ai gọi. Nó chỉ tới địa chỉ công khai: địa chỉ nội bộ, loopback và mạng riêng bị từ chối trong mã, không có công tắc nào mở ra.
Đăng nhập hộ agent. Agent không gõ vào ô mật khẩu, mã xác thực hay số thẻ, nên chủ đăng nhập
hộ nó bằng nút “Mở cửa sổ đăng nhập” trên thẻ Trình duyệt ở trang agent
(agents.md). Cửa sổ là một Chromium có giao diện, mở trên màn
hình của chính máy chạy đội, không trên thiết bị đang xem web, nên người đăng nhập phải ở trước
máy ấy; nó tự đóng sau 15 phút. Trên macOS, server chỉ mở được cửa sổ khi tiến trình của nó thuộc
phiên đang ở trước màn hình và màn hình không khoá. Server khởi động qua SSH hay như một daemon
không có phiên màn hình nào, còn server chạy trong phiên của một người dùng khác với người đang ở
trước màn hình thì không được tính; khi đó thẻ nói lý do thay cho nút. Trên hệ khác, server coi là
có màn hình khi môi trường của nó có DISPLAY hoặc WAYLAND_DISPLAY. Cửa sổ làm gì và đóng khi
nào ở tools.md, đoạn “Cửa sổ đăng nhập”.
Mặc định không agent nào dùng được màn hình của máy. Desktop cần một trình trợ giúp:
MyAgentCrewDesktop.app, một ứng dụng macOS nhỏ (macOS 14 trở lên, không có biểu tượng trên
Dock) chạy trong phiên đăng nhập của chủ máy, giữ hai quyền Ghi màn hình và Trợ năng, và trả lời
server qua một Unix socket. Server không có công tắc chung nào: agent nào ghi computer: "on" thì
có bốn tool computer_*, và mỗi lời gọi tự tìm trình trợ giúp. Tool làm gì và khi nào hỏi người ở
tools.md, khoá của agent ở agents.md.
Giữ mọi agent ở computer: off cho tới khi chủ máy đã mở khoá màn hình và cấp đủ hai quyền,
tức là thẻ “Desktop” ở Quản lý → Kết nối nói trình trợ giúp đang chạy, màn hình không khoá và
cả hai quyền đã cấp. Bật trước đó thì agent có tool, nhưng thay cho ảnh hay thao tác nó chỉ nhận
lời báo máy còn thiếu gì.
scripts/desktop-helper-pack.sh --install # cần bộ công cụ Swift (Xcode hoặc Command Line Tools)
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.my-agent-crew.desktop.plist
desktop-helper/dist/MyAgentCrewDesktop.app (git bỏ qua thư mục này), ký ad hoc với
định danh com.my-agent-crew.desktop rồi kiểm chữ ký. Không có --install thì chỉ đóng gói.
Có --install thì nó chép ứng dụng vào ~/Applications/MyAgentCrewDesktop.app và viết tệp
dịch vụ ~/Library/LaunchAgents/com.my-agent-crew.desktop.plist: chạy ngay khi nạp, chỉ
trong phiên đồ hoạ của người dùng (không có màn hình thì không có gì để chụp), chỉ được dựng
lại sau khi sập, và ghi lời của trình trợ giúp vào
~/Library/Logs/com.my-agent-crew.desktop.log. Trình trợ giúp tự dừng vì một lý do của nó
(thư mục socket mở cho người khác, đã có trình trợ giúp khác ở socket) thì không được dựng
lại: lý do nằm trong log, và lần chạy sau cũng dừng y như thế.MyAgentCrewDesktop. macOS có thể đòi mở lại ứng dụng sau khi cấp Ghi màn hình; khi đó
chạy launchctl kickstart -k gui/$(id -u)/com.my-agent-crew.desktop.computer: "on" (có ngoặc) và computer_apps vào tệp của agent rồi
khởi động lại, hoặc chọn Bật ở trình sửa agent trên web, có hiệu lực từ lượt kế tiếp. Nút “Thử
chụp” trên thẻ “Desktop” ở trang agent chụp đúng như agent sẽ chụp, nên là phép thử cuối.
Tắt cũng ở hai chỗ ấy.Đóng gói lại thì cấp lại quyền. Mỗi lần đóng gói, chữ ký ad hoc đổi, và macOS quên cả hai
quyền đã cấp cho bản trước: thẻ Kết nối trở lại “chưa cấp”. Trong Cài đặt hệ thống, gỡ dòng
MyAgentCrewDesktop cũ ở mục Ghi màn hình và mục Trợ năng, khởi động lại trình trợ giúp bằng lệnh
kickstart ở trên, rồi bấm “Xin quyền” lần nữa hoặc thêm lại ứng dụng ở
~/Applications/MyAgentCrewDesktop.app vào hai mục ấy.
Socket. Mặc định server tìm trình trợ giúp ở <home>/desktop/sock. Đặt chỗ khác bằng
desktop_socket trong config.yaml hoặc MY_AGENT_DESKTOP_SOCKET (biến thắng tệp): đường dẫn
tuyệt đối (~ được mở rộng), không quá 103 byte, sai thì server dừng lúc khởi động. Tệp dịch vụ
do --install viết ghi cứng socket ~/.my-agent-crew/desktop/sock (dạng đường dẫn đầy đủ) sau
--socket, nên với home khác hay socket khác, sửa đối số ấy trong tệp dịch vụ cho trùng rồi nạp
lại (launchctl bootout rồi bootstrap); mỗi lần chạy --install viết lại tệp và xoá mất chỗ
đã sửa.
Trình trợ giúp tự tạo thư mục chứa socket với quyền 700 khi chưa có, để socket ở quyền 600, và
không chịu chạy khi thư mục ấy không thuộc người dùng đang chạy nó hay mở cho nhóm hay người khác,
khi một trình trợ giúp khác đang trả lời ở socket, hoặc khi đường dẫn là một tệp không phải
socket. Phía server, socket không thuộc người dùng chạy server, nằm trong thư mục người khác ghi
được hay là symlink thì mọi lời gọi từ chối với untrusted.
Trước khi bật cho một agent, như với trình duyệt:
<home>/desktop/ đóng với mọi tool tệp và mọi shell_run trong sandbox,
nhưng shell không sandbox với tới socket như mọi tiến trình khác của người dùng máy, và qua đó
vòng qua cả computer_apps lẫn cổng duyệt. Agent bật desktop nên có shell trong sandbox hoặc
không có shell_run (tools.md, đoạn “Thư mục giữ riêng”).computer_screenshot lưu ảnh cả màn hình vào desktop/ trong
workspace của agent, hoặc dưới thư mục đầu tiên của write_paths khi agent có danh sách đó, và
giữ 40 ảnh mới nhất. Ảnh màn hình là dữ liệu riêng: workspace là một repo git thì cho desktop/
vào .gitignore của repo.app_mismatch).Gỡ. Đưa mọi agent về computer: off, rồi:
launchctl bootout gui/$(id -u)/com.my-agent-crew.desktop
rm ~/Library/LaunchAgents/com.my-agent-crew.desktop.plist
rm -rf ~/Applications/MyAgentCrewDesktop.app
Gỡ hai dòng MyAgentCrewDesktop trong Cài đặt hệ thống → Quyền riêng tư & Bảo mật. Server không
cần khởi động lại: agent còn computer: "on" vẫn giữ bốn tool, và mỗi lời gọi trả absent. Ảnh
đã chụp nằm lại trong desktop/ của từng workspace cho tới khi có người xoá.
Bộ cài thật là một plist gọi thẳng uv, không qua script shell:
<!-- ~/Library/LaunchAgents/com.my-agent-crew.server.plist -->
<key>ProgramArguments</key><array>
<string>/opt/homebrew/bin/uv</string><string>run</string>
<string>--env-file</string><string>/Users/<user>/.my-agent-crew/env</string>
<string>python</string><string>-m</string><string>my_agent_crew</string>
<string>--host</string><string>127.0.0.1</string><string>--port</string><string>8765</string>
</array>
<key>WorkingDirectory</key><string>/Users/<user>/workspace/my-agent-crew</string>
<key>EnvironmentVariables</key><dict>
<key>PATH</key><string>/opt/homebrew/bin:/Users/<user>/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>MY_AGENT_HOME</key><string>/Users/<user>/.my-agent-crew</string>
</dict>
<key>KeepAlive</key><true/>
<key>ExitTimeOut</key><integer>45</integer>
<key>StandardOutPath</key><string>/Users/<user>/Library/Logs/my-agent-crew/server.log</string>
<key>StandardErrorPath</key><string>/Users/<user>/Library/Logs/my-agent-crew/server.log</string>
uv run --env-file nạp env thay cho source trong zsh. Đường dẫn tuyệt đối vì launchd không
mở rộng ~ hay $HOME.
Home hoặc repo nằm trên ổ ngoài (/Volumes/...). macOS (TCC) chặn job launchd chạy qua
/bin/zsh hay /bin/sh đọc ổ ngoài: script báo “Operation not permitted” ngay lệnh đầu. Binary
Homebrew như /opt/homebrew/bin/uv thì được phép, và mọi process nó sinh ra cũng vậy. Vì thế
chương trình chính phải là uv, không phải một script zsh. Bản thân launchd cũng không mở được
StandardOutPath trên ổ ngoài; khi đó job thoát 78: EX_CONFIG mà không ghi log nào. Giữ log ở
~/Library/Logs/ trên ổ trong; logs/server.log trong home có thể là symlink tới đó.
run-server.zsh của bản cài cũ vẫn chạy được khi mọi thứ nằm trên ổ trong, nhưng không còn cần.
ExitTimeOut phải dài hơn thời gian bot chờ khi dừng (30 s cho lượt đang chạy + 5 s cho lời
báo “tin bị ngắt”). Mặc định của launchd là 20 s, nên thiếu khoá này thì một lần kickstart -k
giữa lượt dài sẽ SIGKILL trước khi người gửi được báo.
Lệnh hay dùng:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.my-agent-crew.server.plist
launchctl kickstart -k gui/$(id -u)/com.my-agent-crew.server # khởi động lại sau khi pull
curl -s http://127.0.0.1:8765/api/health
Dịch vụ là server duy nhất của home đó. Chạy tay một server thứ hai trên cùng home thì bị từ chối; ngược lại, dịch vụ gặp home đang do một server chạy tay giữ thì thoát mã 1 và được launchd dựng lại cho tới khi server kia dừng.
Linux: systemd user unit với ExecStart=uv run --env-file …/env python -m my_agent_crew --host 127.0.0.1 --port 8765, WorkingDirectory= trỏ tới repo, Restart=always.
cd my-agent-crew && git pull && uv sync
launchctl kickstart -k gui/$(id -u)/com.my-agent-crew.server
Schema SQLite tự thêm cột/bảng khi khởi động; không có bước migrate tay. Đọc docs/agents.md khi nâng phiên bản lớn vì bố cục persona có thể đổi.
Lượt đang chạy lúc khởi động lại được làm tiếp một lần. kickstart -k giữa một lượt cắt lượt đó: lượt web bị cắt ngay, lượt Telegram được chờ tối đa 30 s rồi chat nhận lời báo sẽ làm tiếp (channels.md). Server khởi động kế tiếp mở lại đúng run đó, cùng số tiền đã tiêu và dưới cùng trần chi phí, rồi chạy tiếp từ nhật ký; tool chỉ đọc được gọi lại, còn lệnh gọi nào có thể đã chạy mà đổi trạng thái thì được đóng bằng một ghi chú “không rõ đã chạy hay chưa” để model tự kiểm, không chạy lại ngầm. Lượt đang chờ một quyết định thì vẫn chờ quyết định đó; lượt của cuộc đã hết ngân sách, đã có lượt mới hơn, hoặc đã từng được làm tiếp một lần thì ở lại trạng thái bị ngắt. Chi tiết ở design.md. Vẫn nên khởi động lại lúc không có lượt nào đang chạy khi chọn được. Server chạy với --no-schedule không làm tiếp lượt nào và đóng hẳn những lượt bị cắt, nên dùng cờ đó khi cần xem dữ liệu sau một lần sập mà không muốn lượt cũ chạy tiếp (dừng dịch vụ trước, vì một home chỉ nhận một server mỗi lúc).
Sao lưu agent.sqlite3 trước. Schema chỉ đi một chiều: bản mới thêm bảng và cột (canvas thêm bốn bảng và ba cột, xem system-architecture.md; bản 0.12.0 thêm bảng message_requests và ba cột runs.resumed, messages.turn_notes, queued_messages.request_id; bản 0.15.0 thêm bảng script_store) và không có bước lùi. Tệp chạy ở chế độ WAL, nên chép tệp bằng cp khi server đang chạy có thể thiếu những lần ghi còn nằm trong tệp -wal. Dùng API backup của SQLite, mở nguồn chỉ đọc bằng mode=ro (đừng dùng immutable=1 trên tệp đang chạy):
mkdir -p "$HOME/.my-agent-crew/backups/truoc-nang-cap"
python3 - <<'EOF'
import os, sqlite3
home = os.path.expanduser("~/.my-agent-crew")
src = sqlite3.connect(f"file:{home}/agent.sqlite3?mode=ro", uri=True)
dst = sqlite3.connect(f"{home}/backups/truoc-nang-cap/agent.sqlite3")
src.backup(dst)
dst.close(); src.close()
EOF
Kiểm bản sao bằng cách mở nó bình thường và chạy PRAGMA integrity_check; mở bản sao ở chế độ chỉ đọc có thể báo lỗi dù bản sao vẫn tốt.
Sau khi khởi động lại, tải lại các tab đang mở. Tab mở từ trước vẫn chạy bản web cũ cho tới khi tải lại; thanh “Có bản mới” mời tải lại chứ không ép.
Agent có danh sách tools:. Agent khai tools: trong agent.yaml chỉ có đúng những tool nó liệt kê, nên muốn nó dùng canvas thì thêm tên các tool canvas vào danh sách (canvas.md); agent không khai tools: thì có sẵn. Cuộc trò chuyện giao việc đã mở từ trước lần nâng cấp không có kênh gốc được ghi lại, nên agent con trong đó không ghi được canvas (canvas.md).
Máy đã bật trình duyệt cho agent. Chromium đi theo phiên bản của gói Playwright: sau một lần uv sync đổi phiên bản gói đó, chạy lại uv run playwright install chromium trước khi khởi động lại. Quên bước này thì server vẫn lên, thẻ “Trình duyệt” ở Kết nối báo thiếu trình duyệt kèm đúng lệnh ấy, và các agent mất tool browser_* cho tới lần khởi động sau khi cài (§4b).
Máy đã cài trình trợ giúp desktop. Trình trợ giúp không tự nâng cấp theo server. Khi bản mới
đổi trình trợ giúp (CHANGELOG nói vậy, hoặc lời gọi computer_* trả unknown_command), chạy lại
scripts/desktop-helper-pack.sh --install, rồi
launchctl kickstart -k gui/$(id -u)/com.my-agent-crew.desktop, rồi cấp lại hai quyền vì chữ ký
đã đổi (§4c). Bản mới không đổi trình trợ giúp thì không cần làm gì.
Bản này khác bản trước ở đâu: CHANGELOG.md.
Dự án phát hành bằng tag git có chú thích kèm một GitHub Release, không đẩy lên PyPI —
người dùng cài bằng cách clone rồi uv sync, nên tag là thứ trỏ tới một trạng thái code, còn
Release là chỗ người theo dõi repo đọc bản đó có gì (ghi chú chép từ mục của bản đó trong
CHANGELOG).
Số hiệu theo SemVer, và một số dùng chung cho cả backend lẫn web:
| Loại | Khi nào | Ví dụ ở dự án này |
|---|---|---|
| major | đổi vỡ: lược đồ cần migrate tay, bỏ endpoint, đổi hình dạng config.yaml/agent.yaml |
chưa có |
| minor | thêm tính năng, thêm endpoint, đổi lớn ở UI mà dữ liệu cũ vẫn chạy | v0.4.0 — web UI dựng lại, 8 endpoint mới, bảng mới đều IF NOT EXISTS |
| patch | chỉ sửa lỗi và tài liệu | — |
# 1. Cổng: mọi cổng CI trong một lệnh (testing.md, mục Chạy các cổng). Không tag khi còn một
# cổng đỏ.
./scripts/gates.sh
# 2. Bản minor: chạy bộ kiểm thử hành vi bằng model thật (tốn tiền thật; testing.md, mục Kiểm
# thử hành vi bằng model thật). Ca trượt vì lỗi runtime thì sửa trước khi phát hành; ca
# trượt vì persona là một phát hiện, không chặn phát hành.
uv run python scripts/run_evals.py --runs 3 --max-usd 0.5
# 3. Nâng số ở mọi chỗ ghi phiên bản — phải khớp nhau:
# pyproject.toml · my_agent_crew/__init__.py · uv.lock (mục my-agent-crew)
# web/package.json · web/package-lock.json (hai dòng đầu có "version")
# + dòng "**Phiên bản**" ở mọi tài liệu trong docs/ có dòng này
grep -n 'version' pyproject.toml my_agent_crew/__init__.py web/package.json
grep -n -A1 'name = "my-agent-crew"' uv.lock
grep -n -m2 '"version"' web/package-lock.json
grep -rn '^\*\*Phiên bản\*\*' docs/*.md
uv lock --check # uv.lock khớp pyproject.toml chưa; CI chạy `uv sync` nên không bắt lệch
# 4. Viết mục mới trong CHANGELOG.md: Added / Changed / Fixed / Upgrade notes, thêm link ref
# của bản mới ở cuối tệp và để lại một mục [Unreleased] trống ở trên cùng.
# Nguồn là `git log --oneline vX.Y.Z..HEAD`, nhưng viết theo giá trị cho người dùng,
# không chép nguyên commit message.
# 5. Commit rồi đẩy — để CI chạy thật trước khi tag
git add -A && git commit -m "chore(release): v0.20.0"
git push origin main
# 6. Đợi CI xanh. Chỉ tag khi đã xanh: tag trỏ vào commit đỏ là thứ khó gỡ.
gh run watch
# 7. Tag có chú thích (mọi tag cũ đều là annotated — giữ cho đồng nhất)
git tag -a v0.20.0 -m "v0.20.0"
git push origin v0.20.0
# 8. GitHub Release: ghi chú là mục của bản này trong CHANGELOG (bỏ dòng tiêu đề), cộng một
# dòng kết quả eval nếu đã chạy — chỉ số ca đạt và chi phí, không nội dung ca.
gh release create v0.20.0 --title "v0.20.0 — <một câu nói bản này làm gì>" --notes-file notes.md
Không nên: tag trước khi push (tag trỏ tới commit chưa ai thấy); tag khi CI đang đỏ; nâng số
ở pyproject.toml mà quên web/package.json (hai bên lệch nhau là thứ không có test nào bắt
được) hay uv.lock (chỉ uv lock --check bắt được).
uv run python -m my_agent_crew agent list-templates
uv run python -m my_agent_crew agent add researcher # → agents/researcher/
uv run python -m my_agent_crew agent add fullstack-developer --workspace ~/workspace/x
Sau đó sửa agents/<id>/agent.yaml (routes, tools, schedules, autonomous) và persona (AGENTS.md, SOUL.md). Thêm id vào delegates: của master để master thấy trong roster. Khởi động lại server.
agent.yaml của master:
telegram:
token_env: TELEGRAM_BOT_TOKEN
chat_id: <id chat của bạn>
agent.yaml thì khởi động lại. Poller bắt đầu. Chỉ chat từ chat_id được nhận. Xem channels.md.Sơ đồ .html trong docs/diagrams/ là trang tự chứa: script, style, animation đều inline. Không dán nội dung đó vào .md; thay vào đó .md nhúng bằng <iframe src="diagrams/<tên>.html"> — Jekyll giữ nguyên HTML thô trong markdown, nên sơ đồ chạy ngay trong trang tài liệu. Mỗi sơ đồ kèm link “Mở riêng” và “ảnh tĩnh” (SVG) để trang vẫn đọc được ở nơi iframe bị chặn (GitHub blob view, trình đọc markdown). Bản động chỉ chạy khi docs/ được phục vụ như site tĩnh — mọi cách dưới đây đều làm được vì không cần build gì.
Cách A — GitHub Pages từ /docs (khuyến nghị)
Settings → Pages → Source: main / /docs. Front matter layout: default để Jekyll render .md; các .html và .svg được phục vụ nguyên vẹn. Link diagrams/index.html mở gallery với animation.
Cách B — Site tĩnh bất kỳ (Netlify, Cloudflare Pages, S3, Vercel static)
Trỏ thư mục publish vào docs/. Nếu site không render markdown, dùng gallery diagrams/index.html làm trang vào và đổi link .md thành .html bằng bộ render tuỳ chọn.
Cách C — Máy cá nhân
python3 -m http.server 8080 --directory docs
# http://localhost:8080/diagrams/index.html
Sơ đồ chạy đầy đủ, nhưng link .md giữa các tài liệu không mở được vì không có Jekyll rewrite .md → .html; dùng cách này để xem sơ đồ, cách A để đọc cả bộ.
Chiều cao iframe: trang sơ đồ cao hơn viewBox vì có tiêu đề, thanh view, chú giải và thẻ; đo thật bằng document.documentElement.scrollHeight rồi đặt height hơn số đo một chút. Bộ doc này dùng 1090–1390 px tuỳ sơ đồ.
Không nên: mở .html bằng file:// từ một số trình duyệt chặn script cục bộ; dán nội dung .html vào .md; đổi tên .svg mà không sửa link trong .md và index.html.
Tái tạo sơ đồ: sửa .json, chạy validate → deliver → to-svg theo diagrams/README.md, xoá artifact *.visual-check.*.
docs/diagrams/index.html qua http.server, bấm cả 5 “Mở bản động”.docs/: grep theo tiền tố key OpenRouter, dạng token bot Telegram, chat id, đường dẫn home cá nhân phải rỗng.\.py\b, \.tsx?\b, test_, MAX_[A-Z_]+ trong docs/*.md chỉ được trả về lệnh chạy, đường dẫn cấu hình và ví dụ người dùng._; docs/diagrams/ không bị ảnh hưởng nhưng cần nhớ nếu sau này thêm thư mục con.