본문으로 건너뛰기

Kanban 튜토리얼

Hermes Kanban 시스템이 어떤 상황을 위해 설계되었는지, 네 가지 사용 사례를 대시보드를 브라우저에 열어 둔 상태로 따라가며 살펴보는 단계별 안내입니다. 아직 Kanban overview를 읽지 않았다면 먼저 그 문서부터 보세요. 이 튜토리얼은 task, run, assignee, dispatcher가 무엇인지 알고 있다고 가정합니다.

설정

hermes kanban init           # 선택 사항. 첫 `hermes kanban <anything>` 실행 때 자동 초기화됩니다.
hermes dashboard # 브라우저에서 http://127.0.0.1:9119 를 엽니다.
# 왼쪽 nav에서 Kanban을 클릭합니다.

대시보드는 사용자가 시스템을 지켜보기에 가장 편한 화면입니다. 디스패처가 시작하는 에이전트 워커는 대시보드나 CLI를 보지 않습니다. 워커는 전용 kanban_* 툴셋(kanban_show, kanban_list, kanban_complete, kanban_block, kanban_heartbeat, kanban_comment, kanban_create, kanban_link, kanban_unblock)을 통해 보드를 조작합니다. 대시보드, CLI, 워커 도구 세 표면은 모두 같은 보드별 SQLite DB를 통과합니다. default board는 ~/.hermes/kanban.db, 나중에 만드는 보드는 ~/.hermes/kanban/boards/&lt;slug&gt;/kanban.db를 사용하므로, 어느 쪽에서 변경해도 보드는 일관됩니다.

이 튜토리얼은 전체에서 default 보드를 사용합니다. project/repo/domain별로 격리된 queue를 여러 개 쓰고 싶다면 overview의 Boards (multi-project)를 참고하세요. 같은 CLI / 대시보드 / 워커 flow가 보드별로 적용되며, 워커는 물리적으로 다른 보드의 task를 볼 수 없습니다.

튜토리얼 전체에서 bash label이 붙은 code block은 사용자가 직접 실행하는 command입니다. # worker tool calls label이 붙은 code block은 시작된 워커의 모델이 emit하는 tool call입니다. loop가 end-to-end로 어떻게 끝나는지 보여 주기 위한 것이며, 사용자가 직접 실행할 command가 아닙니다.

보드 한눈에 보기

Kanban board overview

왼쪽에서 오른쪽으로 여섯 column이 있습니다.

  • Triage - 아직 다듬지 않은 raw idea입니다. 누군가 작업하기 전에 specifier가 spec을 구체화합니다. triage card의 ✨ Specify button을 누르거나 chat에서 hermes kanban specify &lt;id&gt; / /kanban specify &lt;id&gt;를 실행하면 auxiliary LLM이 one-liner를 full spec(goal, approach, acceptance criteria)으로 바꾸고 곧바로 todo로 promote합니다. 어떤 model을 사용할지는 config.yamlauxiliary.triage_specifier에서 설정합니다.
  • Todo - 생성되었지만 dependency를 기다리거나 아직 assign되지 않은 task입니다.
  • Ready - assign되었고 dispatcher가 claim하기를 기다리는 task입니다.
  • In progress - 워커가 task를 실행 중입니다. "Lanes by profile"이 켜져 있으면(기본값) 이 column은 assignee별 subgroup으로 나뉘어 각 워커가 무엇을 하고 있는지 한눈에 볼 수 있습니다.
  • Blocked - 워커가 human input을 요청했거나 circuit breaker가 trip된 상태입니다.
  • Done - 완료된 task입니다.

top bar에는 search, tenant, assignee filter가 있고, Lanes by profile toggle과 Nudge dispatcher button이 있습니다. Nudge dispatcher는 daemon의 다음 interval을 기다리지 않고 지금 dispatch tick을 한 번 실행합니다. card를 클릭하면 오른쪽 drawer가 열립니다.

Flat view

profile lane grouping이 시끄럽다면 "Lanes by profile"을 끄세요. In Progress column이 claim time 순서의 single flat list로 접힙니다.

Board with lanes by profile off

Story 1 - solo dev의 feature shipping

기능을 만들고 있다고 합시다. 전형적인 flow는 schema 설계, API 구현, test 작성입니다. parent→child dependency를 가진 세 task입니다.

SCHEMA=$(hermes kanban create "Design auth schema" \
--assignee backend-dev --tenant auth-project --priority 2 \
--body "Design the user/session/token schema for the auth module." \
--json | jq -r .id)

API=$(hermes kanban create "Implement auth API endpoints" \
--assignee backend-dev --tenant auth-project --priority 2 \
--parent $SCHEMA \
--body "POST /register, POST /login, POST /refresh, POST /logout." \
--json | jq -r .id)

hermes kanban create "Write auth integration tests" \
--assignee qa-dev --tenant auth-project --priority 2 \
--parent $API \
--body "Cover happy path, wrong password, expired token, concurrent refresh."

APISCHEMA를 parent로 갖고, testsAPI를 parent로 갖습니다. 따라서 SCHEMAready에서 시작하고, 나머지 둘은 parent가 완료될 때까지 todo에 머뭅니다. 이것이 dependency promotion engine의 역할입니다. test-writing worker는 test할 API가 생기기 전까지 task를 pick up하지 않습니다.

다음 dispatcher tick(기본 60초, 또는 Nudge dispatcher를 누르면 즉시)에서 backend-dev 프로필이 HERMES_KANBAN_TASK=$SCHEMA env를 가진 워커로 spawn됩니다. agent 내부에서 워커의 tool-call loop는 다음과 비슷합니다.

# worker tool calls — NOT commands you run
kanban_show()
# → returns title, body, worker_context, parents, prior attempts, comments

# (worker reads worker_context, uses terminal/file tools to design the schema,
# write migrations, run its own checks, commit — the real work happens here)

kanban_heartbeat(note="schema drafted, writing migrations now")

kanban_complete(
summary="users(id, email, pw_hash), sessions(id, user_id, jti, expires_at); "
"refresh tokens stored as sessions with type='refresh'",
metadata={
"changed_files": ["migrations/001_users.sql", "migrations/002_sessions.sql"],
"decisions": ["bcrypt for hashing", "JWT for session tokens",
"7-day refresh, 15-min access"],
},
)

kanban_show는 기본적으로 task_id$HERMES_KANBAN_TASK로 잡으므로 워커는 자기 id를 몰라도 됩니다. kanban_complete는 summary + metadata를 현재 task_runs row에 기록하고, run을 닫고, task를 done으로 transition합니다. 모두 kanban_db를 통한 atomic hop으로 처리됩니다.

SCHEMAdone이 되면 dependency engine이 API를 자동으로 ready로 promote합니다. API 워커가 pick up되면 kanban_show()를 호출하고 parent handoff에 붙은 SCHEMA summary와 metadata를 보게 됩니다. 그래서 긴 design doc을 다시 읽지 않아도 schema decision을 알 수 있습니다.

보드에서 완료된 schema task를 클릭하면 drawer에 모든 내용이 표시됩니다.

Solo dev — completed schema task drawer

아래쪽의 Run History section이 핵심입니다. attempt 하나: outcome completed, worker @backend-dev, duration, timestamp, 그리고 handoff summary 전체가 있습니다. metadata blob(changed_files, decisions)도 run에 저장되며, 이 parent를 읽는 downstream worker에게 노출됩니다.

terminal에서도 언제든 같은 data를 inspect할 수 있습니다. 아래 command는 사용자가 보드를 들여다보는 것이지 worker가 실행하는 것이 아닙니다.

hermes kanban show $SCHEMA
hermes kanban runs $SCHEMA
# # OUTCOME PROFILE ELAPSED STARTED
# 1 completed backend-dev 0s 2026-04-27 19:34
# → users(id, email, pw_hash), sessions(id, user_id, jti, expires_at); refresh tokens ...

Story 2 - fleet farming

translator, transcriber, copywriter 세 worker가 있고 서로 독립적인 task가 많이 있다고 합시다. 세 worker가 parallel로 task를 가져가며 진행 상황이 보이기를 원합니다. 이것이 가장 단순한 Kanban 사용 사례이며, 원래 design이 최적화한 형태입니다.

작업을 만듭니다.

for lang in Spanish French German; do
hermes kanban create "Translate homepage to $lang" \
--assignee translator --tenant content-ops
done
for i in 1 2 3 4 5; do
hermes kanban create "Transcribe Q3 customer call #$i" \
--assignee transcriber --tenant content-ops
done
for sku in 1001 1002 1003 1004; do
hermes kanban create "Generate product description: SKU-$sku" \
--assignee copywriter --tenant content-ops
done

gateway를 시작하고 자리를 비우면 됩니다. gateway는 embedded dispatcher를 host하고, 같은 kanban.db에서 세 specialist profile의 task를 pick up합니다.

hermes gateway start

보드를 content-ops로 filter하거나 "Transcribe"를 search하면 다음처럼 보입니다.

Fleet view filtered to transcribe tasks

transcribe 두 개는 done, 하나는 running, 두 개는 다음 dispatcher tick을 기다리는 ready입니다. In Progress column은 profile별로 group됩니다("Lanes by profile" 기본값). 그래서 mixed list를 훑지 않고도 각 worker의 active task를 볼 수 있습니다. dispatcher는 현재 task가 완료되는 즉시 다음 ready task를 running으로 promote합니다. 세 daemon이 세 assignee pool을 parallel로 처리하므로 content queue는 추가 human input 없이 drain됩니다.

Story 1에서 설명한 structured handoff는 여기에도 그대로 적용됩니다. translator worker가 call을 완료하면서 kanban_complete(summary="translated 4 pages, style matched existing marketing voice", metadata={"duration_seconds": 720, "tokens_used": 2100})를 emit하면 analytics에도 유용하고, downstream task가 이 task에 의존할 때도 유용합니다.

Story 3 - retry가 있는 role pipeline

flat TODO list보다 Kanban이 빛나는 부분입니다. PM이 spec을 쓰고, engineer가 구현하고, reviewer가 첫 attempt를 reject합니다. engineer가 수정해 다시 시도하고, reviewer가 approve합니다.

auth-project로 filter한 dashboard view:

Pipeline view for a multi-role feature

세 stage chain이 한 번에 보입니다. Spec: password reset flow(DONE, pm), Implement password reset flow(DONE, backend-dev), Review password reset PR(READY, reviewer)입니다. 각 card 아래에는 parent가 초록색으로 표시되고 child가 dependency로 보입니다.

흥미로운 것은 implementation task입니다. 한 번 blocked되었다가 retry되었기 때문입니다. 아래는 각 worker model이 수행하는 tool call로 표현한 전체 three-agent choreography입니다.

# --- PM worker spawns on $SPEC and writes the acceptance criteria ---
# worker tool calls
kanban_show()
kanban_complete(
summary="spec approved; POST /forgot-password sends email, "
"GET /reset/:token renders form, POST /reset applies new password",
metadata={"acceptance": [
"expired token returns 410",
"reused last-3 password returns 400 with message",
"successful reset invalidates all active sessions",
]},
)
# → $SPEC is done; $IMPL auto-promotes from todo to ready

# --- Engineer worker spawns on $IMPL (first attempt) ---
# worker tool calls
kanban_show() # reads $SPEC's summary + acceptance metadata in worker_context
# (engineer writes code, runs tests, opens PR)
# Reviewer feedback arrives — engineer decides the concerns are valid and blocks
kanban_block(
reason="Review: password strength check missing, reset link isn't "
"single-use (can be replayed within 30min)",
)
# → $IMPL transitions to blocked; run 1 closes with outcome='blocked'

이제 사용자(human) 또는 별도 reviewer profile이 block reason을 읽고 fix direction이 명확하다고 판단한 뒤, dashboard의 "Unblock" button 또는 CLI / slash command로 unblock합니다.

hermes kanban unblock $IMPL
# or from a chat: /kanban unblock $IMPL

dispatcher는 $IMPL을 다시 ready로 promote하고, 다음 tick에서 backend-dev worker를 respawn합니다. 이 second spawn은 같은 task의 new run입니다.

# --- Engineer worker spawns on $IMPL (second attempt) ---
# worker tool calls
kanban_show()
# → worker_context now includes the run 1 block reason, so this worker knows
# which two things to fix instead of re-reading the whole spec
# (engineer adds zxcvbn check, makes reset tokens single-use, re-runs tests)
kanban_complete(
summary="added zxcvbn strength check, reset tokens are now single-use "
"(stored + deleted on success)",
metadata={
"changed_files": [
"auth/reset.py",
"auth/tests/test_reset.py",
"migrations/003_single_use_reset_tokens.sql",
],
"tests_run": 11,
"review_iteration": 2,
},
)

implementation task를 클릭하면 drawer에 두 attempt가 보입니다.

Implementation task with two runs — blocked then completed

  • Run 1 - @backend-devblocked로 끝냈습니다. review feedback이 outcome 바로 아래에 있습니다. "password strength check missing, reset link isn't single-use (can be replayed within 30min)".
  • Run 2 - @backend-devcompleted로 끝냈습니다. 새 summary와 새 metadata가 있습니다.

각 run은 task_runs의 row이며 고유한 outcome, summary, metadata를 갖습니다. retry history는 "latest state" task 위에 얹은 부가 개념이 아니라 primary representation입니다. retry worker가 task를 열면 build_worker_context가 prior attempt를 보여 주므로, second-pass worker는 first pass가 왜 blocked되었는지 보고 그 finding을 직접 해결합니다. 처음부터 다시 돌지 않습니다.

다음은 reviewer 차례입니다. Review password reset PR을 열면 다음처럼 보입니다.

Reviewer&#39;s drawer view of the pipeline

parent link는 completed implementation입니다. reviewer worker가 Review password reset PR에서 spawn되고 kanban_show()를 호출하면, 반환된 worker_context에 parent의 most-recent-completed-run summary + metadata가 포함됩니다. 따라서 reviewer는 diff를 보기 전에 "added zxcvbn strength check, reset tokens are now single-use"와 changed files list를 이미 손에 쥐고 시작합니다.

Story 4 - circuit breaker와 crash recovery

현실의 worker는 실패합니다. credential이 없거나, OOM kill이 나거나, transient network error가 날 수 있습니다. dispatcher에는 두 방어선이 있습니다. N번 연속 실패 후 auto-block하여 보드가 영원히 thrash하지 않게 하는 circuit breaker, 그리고 TTL이 끝나기 전에 worker PID가 사라진 task를 reclaim하는 crash detection입니다.

Circuit breaker - permanent-looking failure

profile environment에 AWS_ACCESS_KEY_ID가 없어 worker를 spawn할 수 없는 deploy task:

hermes kanban create "Deploy to staging (missing creds)" \
--assignee deploy-bot --tenant ops

dispatcher가 worker spawn을 시도합니다. spawn이 실패합니다. (RuntimeError: AWS_ACCESS_KEY_ID not set) dispatcher는 claim을 release하고 failure counter를 증가시킨 뒤 다음 tick에서 다시 시도합니다. 세 번 연속 실패하면(기본 failure_limit) circuit이 trip됩니다. task는 outcome gave_up과 함께 blocked가 됩니다. human이 unblock하기 전까지 더 이상 retry하지 않습니다.

blocked task를 클릭합니다.

Circuit breaker — 2 spawn_failed + 1 gave_up

run 세 개가 모두 같은 error를 error field에 갖고 있습니다. 앞의 두 run은 spawn_failed(retryable), 세 번째는 gave_up(terminal)입니다. 위쪽 event log에는 전체 sequence가 보입니다. created → claimed → spawn_failed → claimed → spawn_failed → claimed → gave_up.

terminal에서는:

hermes kanban runs t_ef5d
# # OUTCOME PROFILE ELAPSED STARTED
# 1 spawn_failed deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env
# 2 spawn_failed deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env
# 3 gave_up deploy-bot 0s 2026-04-27 19:34
# ! AWS_ACCESS_KEY_ID not set in deploy-bot env

Telegram / Discord / Slack이 연결되어 있다면 gave_up event에서 gateway notification이 발생하므로 보드를 확인하지 않아도 outage를 알 수 있습니다.

Crash recovery - worker가 실행 중 죽는 경우

때로는 spawn은 성공하지만 worker process가 나중에 죽을 수 있습니다. segfault, OOM, systemctl stop 같은 경우입니다. dispatcher는 kill(pid, 0)을 polling해 dead pid를 감지합니다. claim을 release하고 task를 다시 ready로 돌린 뒤, 다음 tick에서 새 worker에게 넘깁니다.

seed data의 예시는 memory 부족으로 죽은 migration입니다.

# Worker claims, starts scanning 2.4M rows, OOM kills it at ~2.3M
# Dispatcher detects dead pid, releases claim, increments attempt counter
# Retry with a chunked strategy succeeds

drawer는 전체 two-attempt history를 보여 줍니다.

Crash and recovery — 1 crashed + 1 completed

Run 1은 crashed이고 error는 OOM kill at row 2.3M (process 99999 gone)입니다. Run 2는 completed이고 metadata에는 "strategy": "chunked with LIMIT + WHERE id > last_id"가 있습니다. retry worker는 context에서 run 1의 crash를 보고 더 안전한 strategy를 선택했습니다. metadata 덕분에 future observer나 postmortem writer도 무엇이 바뀌었는지 바로 알 수 있습니다.

Structured handoff - summarymetadata가 중요한 이유

위 모든 story에서 worker는 끝날 때 kanban_complete(summary=..., metadata=...)를 호출했습니다. 이는 단순한 장식이 아니라 workflow stage 사이의 primary handoff channel입니다.

task B의 worker가 spawn되어 kanban_show()를 호출하면, 반환되는 worker_context에는 다음이 포함됩니다.

  • B의 prior attempts - previous runs의 outcome, summary, error, metadata. retry worker가 실패한 경로를 반복하지 않게 합니다.
  • Parent task results - 각 parent의 most-recent completed run summary와 metadata. downstream worker는 upstream work가 왜, 어떻게 수행되었는지 봅니다.

이 방식은 flat kanban system에서 흔한 "comment와 작업 output을 뒤져야 하는" 과정을 대체합니다. PM이 spec metadata에 acceptance criteria를 남기면 engineer worker는 parent handoff에서 구조적으로 그것을 봅니다. engineer가 어떤 test를 실행했고 몇 개가 통과했는지 기록하면 reviewer worker는 diff를 열기 전에 그 list를 갖고 시작합니다.

bulk-close guard가 존재하는 이유도 이 data가 per-run이기 때문입니다. 사용자가 CLI에서 hermes kanban complete a b c --summary X를 실행하면 거부됩니다. 같은 summary를 세 task에 copy-paste하는 것은 거의 항상 잘못이기 때문입니다. handoff flag 없이 bulk close하는 것은 "admin task 여러 개를 끝냈다" 같은 일반적인 경우에는 계속 동작합니다. tool surface에는 같은 이유로 bulk variant가 없습니다. kanban_complete는 항상 task 하나씩만 처리합니다.

현재 실행 중인 task inspect

완전성을 위해, 아직 실행 중인 task의 drawer도 봅니다. Story 1의 API implementation task가 backend-dev에게 claim되었지만 아직 complete되지 않은 상태입니다.

Claimed, in-flight task

status는 Running입니다. active run은 Run History section에 outcome active와 함께 표시되고 ended_at은 없습니다. 이 worker가 죽거나 timeout되면 dispatcher가 이 run을 적절한 outcome으로 닫고 다음 claim에서 새 run을 엽니다. attempt row는 사라지지 않습니다.

다음 단계

  • Kanban overview - 전체 data model, event vocabulary, CLI reference
  • hermes kanban --help - 모든 subcommand와 flag
  • hermes kanban watch --kinds completed,gave_up,timed_out - board 전체 terminal event live stream
  • hermes kanban notify-subscribe &lt;task&gt; --platform telegram --chat-id &lt;id&gt; - 특정 task가 끝났을 때 gateway ping 받기