name: kanban-bootstrap description: Set up, restructure, and maintain Hermes Kanban boards following company IT-BA standards — board naming, task hierarchy, dependency linking, and running the lightweight kanban web viewer. trigger: | Trigger when: - User asks to create a kanban board or restructure existing tasks - User asks to set up task hierarchy or fix broken hierarchies - User asks about dependency linking or parent-child relationships - The kanban web viewer (port 9120) needs to be started, restarted, or debugged - User complains about "board stands alone" or "hierarchy doesn't make sense"
nv-office / NV-Office)nova for a project called Nova)Task IDs follow hierarchical dot-notation. The parent is the Project/Phase, children are its sub-tasks:
| Correct | Wrong |
|---|---|
Parent: IT.04 ... |
Parent: IT.02 ... (wrong number) |
Child: IT.04.01 [IT.03] ... |
Child: IT.03 ... (flat, no hierarchy) |
Child: IT.04.02 [IT.09] ... |
Child: IT.09 ... (flat, no hierarchy) |
Key rules:
- Hierarchy is expressed in the title string, NOT through hermes kanban link
- The [original_ID] in brackets links back to the Lark Task Hub ID for cross-reference
- The dot-notation (IT.04 → IT.04.01) makes parent-child visible at a glance in any Kanban view
This is the #1 pitfall in Hermes Kanban. Understand this clearly:
kanban link actually workshermes kanban link <predecessor_id> <successor_id> means:
"successor task cannot start until predecessor task is completed (status=done)"
When you link A→B:
- B is automatically forced to todo status
- B stays todo until A is set to done
- When A is marked done, B auto-promotes to ready
If you link every child to an umbrella parent, ALL children get stuck in todo forever:
- Child needs parent → child in todo
- Parent has children → parent can't complete until children are done
- → Deadlock: nothing can ever complete
✅ GOOD: link only REAL execution dependencies
IT.04.02 [IT.09] CEO chốt thread ID
↓ LINK ──────────────→ IT.04.04 [IT.04] handoff.py MAPPING
↓ LINK ──────────────→ IT.04.05 [IT.05] Telegram thread IDs
IT.04.04 [IT.04] handoff.py MAPPING
↓ LINK ──────────────→ IT.04.06 [IT.06] handoff-task skill
✅ ALSO GOOD: independent tasks with NO links
IT.04.01 [IT.03] Lark options → stays ready (no blockers)
IT.04.03 [IT.08] SOP handoff → stays ready (no blockers)
❌ WRONG: link children to umbrella parent
IT.04 (umbrella) ──link──→ IT.04.01 ← ALL children stuck in todo!
❌ WRONG: link everything to everything
For umbrella/project container tasks:
1. Create the task normally
2. Set it to running (not todo/ready) via hermes kanban claim <id>
3. Do NOT link children to it
4. It sits in "In Progress" as a visual grouping header
Each department needs its own Hermes profile so the dispatcher can auto-route tasks to the correct worker. Without profiles, all tasks are unassigned and skipped by the dispatcher.
# 1. Create profile (auto-creates wrapper script ~/.local/bin/<name>)
hermes profile create <dept-name>
# 2. Copy API keys from default profile (or run <profile> setup)
cp ~/.hermes/.env ~/.hermes/profiles/<dept-name>/.env
# 3. Set description — CRITICAL for dispatcher routing
hermes profile describe <dept-name> --text "<department role description>"
The description is what the dispatcher uses to decide which profile should claim which task. Be specific about what work this profile handles:
hermes profile describe r-and-d --text "R&D — policy research, literature review, data analysis, academic/regulatory research tasks."
# After creating a task, assign it to the right profile:
hermes kanban assign <task_id> <profile_name>
# Dispatch — spawns worker for the assigned profile:
hermes kanban dispatch
# Output: "Spawned: 1 - t_xxxx -> <profile> @ /path/to/workspace"
| Profile | Purpose | Thread |
|---|---|---|
it-ai |
IT & AI — infra, automation, devops | 18 |
r-and-d |
R&D — policy, data analysis | 12 |
str-mkt |
Strategy & Marketing | 14 |
writers |
Copywriting, translation, content | 1591 |
grill-qa |
QA, testing, validation | 20 |
Tasks created WITHOUT --assignee get null/unassigned — the dispatcher SKIPS them. Every task MUST have an owner profile:
# ✅ CORRECT — dispatcher picks it up
/opt/ai-os/products/ceo/scripts/create_task.sh "Task Name" --assignee <profile_name>
# ❌ WRONG — becomes "unassigned", dispatcher skips
hermes kanban create "Task Name"
Wrapper script at /opt/ai-os/products/ceo/scripts/create_task.sh enforces this by rejecting creates without --assignee. Always use this script.
describe --text (NOT per-profile SOUL.md)⚠️ KEY CORRECTION (2026-07-12): Hermes only reads ONE global SOUL.md (~/.hermes/SOUL.md). Per-profile SOUL.md files (~/.hermes/profiles/<name>/SOUL.md) are NOT supported — the engine silently ignores them. Do NOT create per-profile SOUL.md files.
Instead, use hermes profile describe <name> --text "<description>" to define each department's focus and style. This description is what the dispatcher reads to route tasks to the right profile:
hermes profile describe it-ai --text "IT & AI: code, devops, automation, kanban pipeline, system maintenance."
hermes profile describe r-and-d --text "R&D / Policy Lab: policy research, macroeconomics, literature review, APA 7th."
hermes profile describe str-mkt --text "Strategy & Marketing: market strategy, campaigns, SEO, competitor analysis, brand."
hermes profile describe writers --text "Writers / Content: copywriting, translation, editorial, SEO-optimized content."
hermes profile describe grill-qa --text "Grill & QA: testing, bug hunting, cross-review, stress-test, quality assurance."
Layered identity architecture (bottom to top, all compose additively):
| Layer | Scope | Mechanism |
|:------|:------|:----------|
| SOUL.md | Whole instance | ~/.hermes/SOUL.md — identity, style, guardrails for ALL profiles |
| Profile Description | Per department | hermes profile describe <name> --text "..." — routing/dispatcher signal |
| AGENTS.md | Per project repo | ./AGENTS.md — project-specific conventions |
| /personality | Per session | /personality pirate — temporary roleplay override |
None of these layers override another — they all compose additively into the system prompt.
Cách sai: Tạo it-ai-ex và it-ai-sup riêng biệt → 10 profile cho 5 phòng.
Cách đúng (Story 3): Dùng pipeline link để review qua profile khác:
Task A (it-ai: implement) ──link──→ Task B (grill-qa: review)
Task C (str-mkt: approve)
Profile executor IMPLEMENT → complete. Profile reviewer (grill-qa) REVIEW trước khi thực hiện bàn giao. Pipeline link làm nhiệm vụ phân luồng.
Khi nhận yêu cầu chạy song song các khối công việc lớn thuộc các chuyên môn khác nhau (ví dụ: IT nâng cấp dashboard và R&D hệ thống hóa tri thức):
1. Phân tách task có cấu trúc: Tạo cây task rõ ràng với sequence ID tương ứng từng phòng ban (IT.KBN.01 và RD.BRN.01).
2. Gán Assignee chuẩn xác: Bắt buộc truyền tham số assignee khớp với keys trong config/departments.json ngay khi tạo task để tránh task bị bỏ quên (unassigned).
3. Dispatch Subagents song song: Sử dụng công cụ delegate_task để spawn các sub-agents với vai trò riêng biệt (IT Agent và R&D Agent), cung cấp context chuyên biệt chứa các Task ID tương ứng để tránh bị chồng chéo ngữ cảnh. Các sub-agents chạy độc lập trong background và tự cập nhật trạng thái Kanban của mình.
When tasks are chained via kanban link, the auto-promotion mechanism makes the workflow fully automatic:
- When task A completes with --result, the dispatcher promotes linked task B from todo → ready.
- CRITICAL: For HUMAN-in-the-loop review, do NOT link B directly. Instead, create a separate task for review.
If a task output requires CEO approval (e.g., ingest results, master files):
TASK.01 (Execution) and TASK.01.RV (Review).TASK.01.RV -> TASK.02 (Next step). TASK.01 completes -> Set status to blocked + kind=needs_input.TASK.01.TASK.01.RV to ready, then proceeds to TASK.02.NEVER manually create duplicate tasks named 'Review' to replace statuses. Use standard status transitions (blocked -> unblock).
If a task shows as ready but needs human review:
- CLI: hermes kanban block <task_id> --kind needs_input --reason "Review needed at <URL>"
- Effect: CLI status becomes blocked and moves to the "Blocked" column automatically. Gateway avoids auto-promoting tasks with block_kind=needs_input.
# 1. Link tasks in execution order
hermes kanban link t_IT01_part1 t_IT01_part2 # part1 → part2
# 2. When part1 completes with --result + --metadata
hermes kanban complete t_IT01_part1 \
--result "Summary of work done" \
--metadata '{"files": ["/path/to/output"]}'
# 3. Dispatch promotes part2 from todo → ready automatically
hermes kanban dispatch
# Output: Promoted: 1 - t_IT01_part2 (was todo, now ready)
# 4. When worker claims part2 and calls kanban context
hermes kanban context t_IT01_part2
# → Shows parent's summary + metadata in worker_context
Để hệ thống tự động dispatch và spawn các worker chạy ngầm mà không cần con người chạy CLI thủ công:
# Khởi chạy gateway nền
hermes gateway start
Để dashboard hiển thị tiến độ thời gian thực, trong code worker phải chèn:
# Cập nhật tiến độ giữa chừng (không cần kết thúc task)
kanban_heartbeat(note="Đang chạy bước 2/3: Cấu hình database...")
Khi gọi kanban_complete, bắt buộc dùng schema metadata để Agent sau kế thừa:
kanban_complete(
summary="Tóm tắt công việc đã xong",
metadata={
"changed_files": ["path/file1", "path/file2"],
"decisions": ["quyết định 1", "quyết định 2"],
"duration_seconds": 120,
"status": "completed"
}
)
changed_files: Truy vết code.decisions: Lưu lại suy luận giúp Agent sau không cần đọc lại tài liệu dài.duration_seconds: Đánh giá hiệu suất.This is the key mechanism that replaces manual handoff messages:
--result and --metadata, that data is stored in SQLitelink) is claimed, calling kanban context B returns A's result + metadata# When worker needs human input:
hermes kanban block <task_id>
hermes kanban comment <task_id> "BLOCKER: reason"
# Human unblocks:
hermes kanban unblock <task_id>
# Dispatcher re-promotes to ready, new worker spawns
hermes kanban dispatch
# → Second run reads block reason from prior run's events
# Delete old board entirely (if starting fresh)
hermes kanban boards rm <slug> --delete
# Or clear tasks from within the board
python3 -c "import sqlite3; c=sqlite3.connect('/root/.hermes/kanban/boards/<slug>/kanban.db'); c.execute('DELETE FROM tasks'); c.commit()"
# 1. Create board (general workspace name)
hermes kanban boards create nv-office --name "NV-Office"
# 2. Switch to it
hermes kanban boards switch nv-office
# 3. Create umbrella (parent) task -- running state
umbrella=$(hermes kanban create 'IT.04 Dự án Tái cấu trúc Vận hành Nova (Phase 1)' \
--body 'Project Dossier: projects/IT.04.md' \
--assignee default --priority 0 --json)
umbrella_id=$(echo "$umbrella" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
hermes kanban claim "$umbrella_id"
# Independent (no deps) -- immediately ready
hermes kanban create 'IT.04.01 [IT.03] Cập nhật department options trên Lark Task Hub' --assignee default --priority 1
# Dependent on CEO chốt -- link later
t09=$(hermes kanban create 'IT.04.02 [IT.09] Xác nhận thread ID R&D (cần CEO chốt)' --assignee default --priority 1 --json)
t09_id=$(echo "$t09" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# ... create remaining tasks, then link REAL deps only
hermes kanban link "$t09_id" "$t04_id" # CEO chốt → handoff.py
hermes kanban link "$t09_id" "$t05_id" # CEO chốt → Telegram
hermes kanban list
# Expected: umbrella=running, independent=ready, dep-blocked=todo/blocked
A lightweight HTTP server that reads the Hermes SQLite DB directly and serves a Kanban column UI, bypassing the Dashboard's Host Header and Basic Auth issues. It also serves as a file browser and Markdown viewer.
| File | Purpose |
|---|---|
kanban_server.py |
Python HTTP server, reads kanban.db, serves HTML+JSON, and static files |
To avoid "Invalid Host header" errors from the official Hermes Dashboard while exposing both it and the custom Kanban OS via Tailscale Funnel:
1. Run Nginx listening on 127.0.0.1:9121.
2. Configure Nginx to rewrite the Host header to 127.0.0.1:9119 before proxying to the official dashboard (proxy_set_header Host 127.0.0.1:9119;).
3. Map /kanban-os/ to proxy to the custom Python server (proxy_pass http://127.0.0.1:9120/;).
4. Map /files/ to proxy to the custom Python server (proxy_pass http://127.0.0.1:9120/files/;).
5. Run Tailscale Funnel pointing ONLY to the Nginx port: tailscale funnel --bg 9121.
This allows a single Tailscale URL to serve:
- / -> Official Hermes Dashboard (header bypass applied)
- /kanban-os/ -> Custom Kanban web viewer
- /files/ -> Custom file browser & Markdown renderer
Note: You may need to inject a navigation bar into the official dashboard using Nginx's sub_filter module so users can switch between them.
# Kill existing if port is stuck
lsof -ti:9120 | xargs -r kill -9
# Start fresh (background)
python3 /opt/ai-os/products/ceo/kanban_server.py
kanban_server.py)/root/.hermes/kanban/boards/<slug>/kanban.db (set in script)http://<tailscale-ip>:9120 (no auth — only reachable within Tailnet)http://<tailscale-ip>:9120/api/tasks returns JSON@assignee profile (mỗi profile là 1 dòng ngang riêng, bên trong chia 5 cột trạng thái). Giúp CEO quan sát workload từng phòng ban.@assignee — chọn 1 profile để chỉ thấy task của profile đó.Done để giảm noise./files/ route): Duyệt trực tiếp file hệ thống tại VPS./root./root và /opt/ai-os/products/ceo (để hỗ trợ symlink như ceo-project).markdown với các navigation breadcrumb.socketserver.ThreadingTCPServer thay vì TCPServer để tránh nghẽn luồng xử lý (single-thread deadlock) khi một request lỗi làm toàn bộ server bị đứng.do_GET phải luôn được xử lý và gửi phản hồi lỗi (ví dụ: HTTP 500 kèm chi tiết lỗi) để giải phóng kết nối, tuyệt đối không để treo kết nối (hang).html đã import (ví dụ: dùng board_html, html_page thay vì html = '...').send_response -> send_header -> end_headers -> wfile.write. Tránh gọi status code trước khi khai báo header sai cách dẫn đến trả về trang trắng (HTTP 200 rỗng).ceo-project trỏ ra ngoài /root (ví dụ: /opt/ai-os/products/ceo), os.path.realpath sẽ giải nén symlink ra path thật. Phải whitelist cả path gốc vật lý của symlink để tránh bị chặn lỗi forbidden 403.//) khi duyệt thư mục bằng cách strip("/") biến path tương đối và định dạng prefix sạch sẽ.favicon.ico trả về 404 để tránh làm nhiễu luồng xử lý chính.Edit kanban_server.py and update the columns dict in the embedded JavaScript:
const columns = {"ready":"Ready","todo":"To do","running":"In progress","blocked":"Blocked","done":"Done"};
| Hermes Status | Kanban Column | Meaning | CLI Command |
|---|---|---|---|
ready |
Ready | Available for dispatch; no blockers | Default on create |
todo |
To do | Waiting on dependency (auto-linked) | Auto when parent-linked |
running |
In Progress | Claimed by a worker | hermes kanban claim <id> |
blocked |
Blocked | Needs human input | hermes kanban block <id> --reason "..." |
done |
Done | Completed | hermes kanban complete <id> --result "..." |
IT.03, IT.04, IT.09). Always use parent.Child notation.lsof -ti:9120 | xargs -r kill -9).--assignee sẽ bị null — dispatcher skip. Dùng wrapper script /opt/ai-os/products/ceo/scripts/create_task.sh để enforce. Không gọi hermes kanban create trực tiếp.unassigned, swimlane view sẽ khó đọc. Luôn set assignee.hermes kanban link tạo HARD dep — child bị khoá cho đến khi parent done. KHÔNG link umbrella với children. Hierarchy chỉ qua dot-notation tên task.renderCardHtml), tránh định nghĩa trùng lặp hàm làm hàm cũ bị ghi đè không mong muốn và trả về undefined.send_response -> send_header -> end_headers -> wfile.write. Gọi status code trước khi khai báo header sai cách có thể gây lỗi stream và trả về trang trắng (HTTP 200 rỗng).html đã import (ví dụ html = "..."), gây ra lỗi runtime cannot access local variable 'html' where it is not associated with a value khi các module con gọi html.escape. Nên dùng tên riêng biệt như board_html hoặc html_page./files/ tới /root mà dự án lại nằm ở /opt/... (được liên kết bằng Symlink ceo-project), Python os.path.realpath sẽ giải nén symlink ra path vật lý thực tế. Cần cấu hình whitelist bảo mật cho cả thư mục symlink đích (/opt/ai-os/products/ceo) để tránh bị server cự tuyệt trả về lỗi forbidden 403.kill $(lsof -ti:9119) && hermes dashboard --port 9119 --host 127.0.0.1.ready instead of running: Umbrella tasks should be running (claimed) so they sit in "In Progress" as active containers, not "Ready" where they look like they need dispatch.| Hermes Status | Kanban Column | Meaning | CLI Command |
|---|---|---|---|
ready |
Ready | Available for dispatch; no blockers | Default on create |
todo |
To do | Waiting on dependency (auto-linked) | Auto when parent-linked |
running |
In Progress | Claimed by a worker | hermes kanban claim <id> |
blocked |
Blocked | Needs human input | hermes kanban block <id> --reason "..." |
done |
Done | Completed | hermes kanban complete <id> --result "..." |