hermes-war-room-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hermes War Room UI

Hermes War Room UI

Skill by ara.so — Hermes Skills collection.
Hermes War Room is a browser-based visual dashboard for managing Hermes Agent's multi-profile orchestration and kanban delegation system. It provides real-time monitoring of agent operatives, task boards, and mission coordination—transforming command-line agent orchestration into a visual "operations floor."
ara.so开发的Skill——Hermes Skills集合。
Hermes War Room是一个基于浏览器的可视化仪表板,用于管理Hermes Agent的多配置文件编排和看板委派系统。它提供对Agent执行体、任务看板和任务协作的实时监控,将命令行式的Agent编排转化为可视化的“操作指挥中心”。

What It Does

功能介绍

The War Room wraps the Hermes CLI with a Vue/Nuxt interface that:
  • Visualizes active agents as operatives on a floor with live status
  • Displays kanban tasks in a 4-column board (Todo/Ready/Running/Blocked)
  • Manages missions via chat with an orchestrator agent
  • Auto-nudges the orchestrator when delegated tasks complete
  • Edits profiles (SOUL.md, skills, tools) directly from the UI
  • Tracks delegation chains showing parent/child task relationships
It reads from
~/.hermes/kanban.db
and
~/.hermes/profiles/
, shells out to the
hermes
CLI, and persists UI state in its own SQLite database.
该指挥中心基于Vue/Nuxt界面封装Hermes CLI,具备以下功能:
  • 可视化活跃Agent:将Agent展示为指挥中心内的执行体,显示实时状态
  • 看板任务展示:以四列看板(待办/就绪/运行中/阻塞)展示任务
  • 任务协作管理:通过与编排Agent对话管理任务
  • 自动提醒编排Agent:当委派任务完成时自动通知编排Agent
  • UI直接编辑配置文件:可在界面中直接编辑SOUL.md、skills和tools
  • 跟踪委派链:展示父/子任务的关联关系
它读取
~/.hermes/kanban.db
~/.hermes/profiles/
目录,调用
hermes
CLI,并将UI状态存储在自身的SQLite数据库中。

Installation

安装步骤

Quick Start (Production Binary)

快速启动(生产二进制包)

bash
undefined
bash
undefined

Download and extract the latest release

下载并解压最新版本

mkdir -p ~/hermes-war-room && cd ~/hermes-war-room curl -L -o hermes-war-room.tar.gz
https://github.com/Naroh091/hermes-war-room/releases/latest/download/hermes-war-room.tar.gz tar xzf hermes-war-room.tar.gz
mkdir -p ~/hermes-war-room && cd ~/hermes-war-room curl -L -o hermes-war-room.tar.gz
https://github.com/Naroh091/hermes-war-room/releases/latest/download/hermes-war-room.tar.gz tar xzf hermes-war-room.tar.gz

Run (requires Hermes CLI installed and Node 22+)

运行(需已安装Hermes CLI和Node 22+)

HERMES_HOME=$HOME/.hermes NITRO_HOST=127.0.0.1 NITRO_PORT=3000
node .output/server/index.mjs

Open `http://localhost:3000` in your browser.
HERMES_HOME=$HOME/.hermes NITRO_HOST=127.0.0.1 NITRO_PORT=3000
node .output/server/index.mjs

在浏览器中打开`http://localhost:3000`。

Development Setup

开发环境搭建

bash
git clone https://github.com/Naroh091/hermes-war-room.git
cd hermes-war-room
pnpm install
pnpm dev
The dev server runs on
http://localhost:3000
.
bash
git clone https://github.com/Naroh091/hermes-war-room.git
cd hermes-war-room
pnpm install
pnpm dev
开发服务器运行在
http://localhost:3000

Environment Variables

环境变量

bash
undefined
bash
undefined

Required

必填项

HERMES_HOME=$HOME/.hermes # Path to Hermes profiles and kanban DB NITRO_HOST=127.0.0.1 # Bind address NITRO_PORT=3000 # Port
HERMES_HOME=$HOME/.hermes # Hermes配置文件和看板数据库路径 NITRO_HOST=127.0.0.1 # 绑定地址 NITRO_PORT=3000 # 端口

Optional

可选项

WAR_ROOM_DB=./data/war-room.db # War Room's own SQLite database NODE_ENV=production # Production/development mode
undefined
WAR_ROOM_DB=./data/war-room.db # War Room自身的SQLite数据库路径 NODE_ENV=production # 生产/开发模式
undefined

Key Concepts

核心概念

Operatives (Profiles)

执行体(配置文件)

Each Hermes profile becomes an "operative" in the War Room:
vue
<!-- Components displaying operatives -->
<OperativeWorkstation
  :operative="operative"
  :current-task="currentTask"
  @click="openDossier"
/>
Operatives have:
  • Callsign: Display name (e.g., "LIDER", "INVESTIGADOR")
  • Avatar: Notionist character image
  • Color: UI accent color
  • Status: Standing by / Working / Blocked
  • Profile slug: Maps to
    ~/.hermes/profiles/<slug>/
每个Hermes配置文件在指挥中心中对应一个“执行体”:
vue
<!-- 展示执行体的组件 -->
<OperativeWorkstation
  :operative="operative"
  :current-task="currentTask"
  @click="openDossier"
/>
执行体包含以下属性:
  • 呼号:显示名称(例如:"LIDER", "INVESTIGADOR")
  • 头像:Notionist风格的角色图片
  • 颜色:UI强调色
  • 状态:待命/工作中/阻塞
  • 配置文件别名:对应
    ~/.hermes/profiles/<slug>/
    路径

Orchestrator Pattern

编排模式

Convention is to have one "leader" profile that routes but never executes:
markdown
<!-- profiles/lider/SOUL.md -->
You are the mission orchestrator. Your job is to:
1. Decompose user goals into discrete tasks
2. Delegate each task via kanban to the right specialist
3. Never do the work yourself
4. Summarize results when workers complete tasks

Always use the terminal tool to create kanban tasks:
hermes kanban create --assignee <worker> --title "..." --body "..." --json
Workers have full tool access and execute:
markdown
<!-- profiles/investigador/SOUL.md -->
You are a research specialist. When assigned a kanban task:
1. Execute the research using available tools
2. Complete the task with: hermes kanban complete <id> --summary "..."
常规配置是设置一个“领导者”配置文件,它仅负责路由任务,从不执行任务
markdown
<!-- profiles/lider/SOUL.md -->
你是任务编排者。你的职责是:
1. 将用户目标分解为独立任务
2. 通过看板将每个任务委派给合适的专家
3. 绝不亲自执行任务
4. 当执行者完成任务后汇总结果

必须使用终端工具创建看板任务:
hermes kanban create --assignee <worker> --title "..." --body "..." --json
执行者拥有完整的工具权限并执行任务:
markdown
<!-- profiles/investigador/SOUL.md -->
你是研究专家。当收到看板任务时:
1. 使用可用工具执行研究
2. 通过以下命令完成任务:hermes kanban complete <id> --summary "..."

Missions

任务协作

A mission is a conversation thread with an orchestrator:
typescript
// server/api/missions/create.post.ts
export default defineEventHandler(async (event) => {
  const { orchestratorId, title } = await readBody(event)
  
  const mission = await db.insert(missions).values({
    orchestrator_id: orchestratorId,
    title: title,
    status: 'open',
    created_at: new Date()
  }).returning()
  
  return mission[0]
})
Messages are sent via ACP sessions that persist across turns.
任务协作是与编排Agent的对话线程:
typescript
// server/api/missions/create.post.ts
export default defineEventHandler(async (event) => {
  const { orchestratorId, title } = await readBody(event)
  
  const mission = await db.insert(missions).values({
    orchestrator_id: orchestratorId,
    title: title,
    status: 'open',
    created_at: new Date()
  }).returning()
  
  return mission[0]
})
消息通过ACP会话发送,会话会在多轮对话中保持持久化。

Server API Endpoints

服务器API端点

Missions

任务协作

typescript
// Create a new mission
POST /api/missions/create
{
  "orchestratorId": 1,
  "title": "Research competitor pricing"
}

// Send a message (returns SSE stream)
POST /api/missions/:id/message
{
  "content": "Find pricing for Acme Corp products"
}

// Get mission history
GET /api/missions/:id
typescript
// 创建新任务协作
POST /api/missions/create
{
  "orchestratorId": 1,
  "title": "研究竞争对手定价"
}

// 发送消息(返回SSE流)
POST /api/missions/:id/message
{
  "content": "查找Acme Corp产品的定价"
}

// 获取任务协作历史
GET /api/missions/:id

Operatives

执行体

typescript
// List all operatives
GET /api/operatives

// Get operative details with current task
GET /api/operatives/:id

// Update operative (callsign, avatar, color, active)
PATCH /api/operatives/:id
{
  "callsign": "RESEARCHER",
  "color": "#3b82f6",
  "active": true
}

// Retrain (update SOUL.md, skills, tools)
POST /api/operatives/:id/retrain
{
  "soul": "You are a specialized legal analyst...",
  "skills": ["research", "analysis"],
  "mcpServers": ["filesystem", "brave-search"]
}
typescript
// 列出所有执行体
GET /api/operatives

// 获取执行体详情及当前任务
GET /api/operatives/:id

// 更新执行体(呼号、头像、颜色、激活状态)
PATCH /api/operatives/:id
{
  "callsign": "RESEARCHER",
  "color": "#3b82f6",
  "active": true
}

// 重新训练(更新SOUL.md、skills、tools)
POST /api/operatives/:id/retrain
{
  "soul": "你是专业的法律分析师...",
  "skills": ["research", "analysis"],
  "mcpServers": ["filesystem", "brave-search"]
}

Kanban

看板

typescript
// Get all tasks for the board view
GET /api/kanban/tasks

// Get tasks for a specific operative
GET /api/kanban/tasks?assignee=investigador

// Watch a task (auto-nudge when complete)
POST /api/kanban/watch
{
  "taskId": "task_abc123",
  "missionId": 5
}
typescript
// 获取看板视图的所有任务
GET /api/kanban/tasks

// 获取指定执行体的任务
GET /api/kanban/tasks?assignee=investigador

// 监控任务(完成时自动提醒)
POST /api/kanban/watch
{
  "taskId": "task_abc123",
  "missionId": 5
}

Creating an Orchestration Team

创建编排团队

1. Set Up Profiles

1. 设置配置文件

Create an orchestrator and workers via Hermes CLI:
bash
undefined
通过Hermes CLI创建编排者和执行者:
bash
undefined

Create orchestrator

创建编排者

hermes profile create lider --model claude-3-5-sonnet-20241022
hermes profile create lider --model claude-3-5-sonnet-20241022

Create specialist workers

创建专业执行者

hermes profile create investigador --model claude-3-5-sonnet-20241022 hermes profile create legal --model claude-3-5-sonnet-20241022 hermes profile create writer --model gpt-4o
undefined
hermes profile create investigador --model claude-3-5-sonnet-20241022 hermes profile create legal --model claude-3-5-sonnet-20241022 hermes profile create writer --model gpt-4o
undefined

2. Configure SOULs

2. 配置SOUL文件

Edit
~/.hermes/profiles/lider/SOUL.md
:
markdown
undefined
编辑
~/.hermes/profiles/lider/SOUL.md
markdown
undefined

Mission Orchestrator

任务编排者

You coordinate a team of specialists. When the user gives you a goal:
  1. Break it into discrete, parallelizable tasks
  2. Assign each task to the right specialist via kanban
  3. Never do the work yourself
  4. Wait for task completion notifications
  5. Synthesize results into a final answer
你负责协调一组专家。当用户给出目标时:
  1. 将目标分解为可并行处理的独立任务
  2. 通过看板将每个任务分配给合适的专家
  3. 绝不亲自执行任务
  4. 等待任务完成通知
  5. 将结果整合为最终答案

Available Team

可用团队成员

  • investigador: Research, data gathering, fact-checking
  • legal: Legal analysis, compliance review
  • writer: Content creation, documentation
  • investigador:研究、数据收集、事实核查
  • legal:法律分析、合规审查
  • writer:内容创作、文档编写

Task Creation

任务创建格式

Always use this exact format:
bash
hermes kanban create \
  --assignee <specialist> \
  --title "<concise title>" \
  --body "<detailed instructions>" \
  --json

Edit `~/.hermes/profiles/investigador/SOUL.md`:

```markdown
必须使用以下精确格式:
bash
hermes kanban create \
  --assignee <specialist> \
  --title "<简洁标题>" \
  --body "<详细说明>" \
  --json

编辑`~/.hermes/profiles/investigador/SOUL.md`:

```markdown

Research Specialist

研究专家

You execute research tasks assigned via kanban.
When you receive a task:
  1. Read the task body for full context
  2. Use available tools (search, filesystem) to gather information
  3. Complete with a detailed summary:
    bash
    hermes kanban complete <task_id> --summary "<findings>"
undefined
你负责执行通过看板委派的研究任务。
收到任务时:
  1. 阅读任务主体获取完整上下文
  2. 使用可用工具(搜索、文件系统)收集信息
  3. 通过以下命令完成任务并提交详细摘要:
    bash
    hermes kanban complete <task_id> --summary "<研究结果>"
undefined

3. Configure Skills in War Room UI

3. 在War Room UI中配置Skills

  1. Navigate to
    /team
  2. Click the operative's badge
  3. Click "Retrain"
  4. Enable skills:
    • Orchestrator:
      terminal
      (for kanban),
      reasoning
    • Workers:
      terminal
      ,
      filesystem
      ,
      search
      ,
      reasoning
      , etc.
  5. Add MCP servers as needed
  6. Save
  1. 导航到
    /team
    页面
  2. 点击执行体的徽章
  3. 点击“重新训练”
  4. 启用对应Skills:
    • 编排者:
      terminal
      (用于看板)、
      reasoning
    • 执行者:
      terminal
      filesystem
      search
      reasoning
  5. 根据需要添加MCP服务器
  6. 保存配置

4. Start the Dispatcher

4. 启动调度器

The dispatcher claims
ready
tasks and hands them to workers:
bash
undefined
调度器会认领“就绪”状态的任务并分配给执行者:
bash
undefined

In a tmux/screen session

在tmux/screen会话中运行

hermes gateway

Without the dispatcher, tasks will stay in "ready" state.
hermes gateway

如果没有调度器,任务将一直处于“就绪”状态。

Real Usage Example

实际使用示例

Complete Flow: Research Mission

完整流程:研究任务协作

typescript
// 1. Frontend: Create mission
const mission = await $fetch('/api/missions/create', {
  method: 'POST',
  body: {
    orchestratorId: 1, // lider
    title: 'Market research for Product X'
  }
})

// 2. Frontend: Send initial brief
const eventSource = new EventSource(
  `/api/missions/${mission.id}/message`,
  {
    method: 'POST',
    body: JSON.stringify({
      content: 'Research our top 3 competitors: pricing, features, and market position'
    })
  }
)

eventSource.addEventListener('tool_call', (event) => {
  const data = JSON.parse(event.data)
  if (data.name === 'terminal' && data.input.includes('kanban create')) {
    console.log('Orchestrator delegating task...')
  }
})

// 3. Backend: Orchestrator creates tasks
// (Automatically intercepted by War Room)
// hermes kanban create --assignee investigador --title "Research Competitor A" --json
// hermes kanban create --assignee investigador --title "Research Competitor B" --json

// 4. Backend: Workers process tasks
// investigador picks up tasks via dispatcher, completes them

// 5. Backend: Auto-nudge when tasks complete
// War Room injects system message:
// "Tasks task_abc123, task_def456 completed. Summarize for the user."

// 6. Frontend: Orchestrator synthesizes response
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data)
  console.log('Orchestrator:', data.content)
  // "Based on the research, here's what we found about your competitors..."
})
typescript
// 1. 前端:创建任务协作
const mission = await $fetch('/api/missions/create', {
  method: 'POST',
  body: {
    orchestratorId: 1, // lider
    title: '产品X的市场调研'
  }
})

// 2. 前端:发送初始简报
const eventSource = new EventSource(
  `/api/missions/${mission.id}/message`,
  {
    method: 'POST',
    body: JSON.stringify({
      content: '研究我们的三大竞争对手:定价、功能和市场地位'
    })
  }
)

eventSource.addEventListener('tool_call', (event) => {
  const data = JSON.parse(event.data)
  if (data.name === 'terminal' && data.input.includes('kanban create')) {
    console.log('编排者正在委派任务...')
  }
})

// 3. 后端:编排者创建任务
// (由War Room自动拦截)
// hermes kanban create --assignee investigador --title "研究竞争对手A" --json
// hermes kanban create --assignee investigador --title "研究竞争对手B" --json

// 4. 后端:执行者处理任务
// investigador通过调度器领取任务并完成

// 5. 后端:任务完成时自动提醒
// War Room注入系统消息:
// "任务task_abc123、task_def456已完成。请为用户汇总结果。"

// 6. 前端:编排者整合响应
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data)
  console.log('编排者回复:', data.content)
  // "根据调研结果,以下是我们对竞争对手的分析..."
})

Server-Side Task Watcher

服务器端任务监控器

typescript
// server/utils/taskWatcher.ts
import { db } from './db'
import { watchedTasks, missions } from './schema'

export async function pollWatchedTasks() {
  const watched = await db.select().from(watchedTasks).where(
    eq(watchedTasks.notified, false)
  )
  
  for (const watch of watched) {
    const task = await getKanbanTask(watch.task_id)
    
    if (task.status === 'done' || task.status === 'blocked') {
      // Inject system message into orchestrator session
      const mission = await db.select().from(missions).where(
        eq(missions.id, watch.mission_id)
      ).get()
      
      await sendSystemMessage(mission.acp_session_id, {
        role: 'system',
        content: `Task ${watch.task_id} is now ${task.status}. Summary: ${task.summary}. Please integrate this into your response to the user.`
      })
      
      // Mark as notified
      await db.update(watchedTasks).set({ notified: true }).where(
        eq(watchedTasks.id, watch.id)
      )
    }
  }
}

// Run every 5 seconds
setInterval(pollWatchedTasks, 5000)
typescript
// server/utils/taskWatcher.ts
import { db } from './db'
import { watchedTasks, missions } from './schema'

export async function pollWatchedTasks() {
  const watched = await db.select().from(watchedTasks).where(
    eq(watchedTasks.notified, false)
  )
  
  for (const watch of watched) {
    const task = await getKanbanTask(watch.task_id)
    
    if (task.status === 'done' || task.status === 'blocked') {
      // 向编排者会话注入系统消息
      const mission = await db.select().from(missions).where(
        eq(missions.id, watch.mission_id)
      ).get()
      
      await sendSystemMessage(mission.acp_session_id, {
        role: 'system',
        content: `任务${watch.task_id}当前状态为${task.status}。摘要:${task.summary}。请将此内容整合到给用户的回复中。`
      })
      
      // 标记为已通知
      await db.update(watchedTasks).set({ notified: true }).where(
        eq(watchedTasks.id, watch.id)
      )
    }
  }
}

// 每5秒运行一次
setInterval(pollWatchedTasks, 5000)

Composables (Frontend)

组合式函数(前端)

useMission

useMission

vue
<script setup>
const { mission, messages, sendMessage, loading } = useMission(missionId)

async function briefOrchestrator() {
  await sendMessage('Analyze legal compliance for EU markets')
}
</script>

<template>
  <div>
    <ChatMessage
      v-for="msg in messages"
      :key="msg.id"
      :message="msg"
    />
    <ChatInput @send="sendMessage" :disabled="loading" />
  </div>
</template>
vue
<script setup>
const { mission, messages, sendMessage, loading } = useMission(missionId)

async function briefOrchestrator() {
  await sendMessage('分析欧盟市场的合规性')
}
</script>

<template>
  <div>
    <ChatMessage
      v-for="msg in messages"
      :key="msg.id"
      :message="msg"
    />
    <ChatInput @send="sendMessage" :disabled="loading" />
  </div>
</template>

useOperatives

useOperatives

vue
<script setup>
const { operatives, activeOperatives, refresh } = useOperatives()

const workers = computed(() => 
  activeOperatives.value.filter(op => op.profile_slug !== 'lider')
)
</script>

<template>
  <OperativeWorkstation
    v-for="op in workers"
    :key="op.id"
    :operative="op"
  />
</template>
vue
<script setup>
const { operatives, activeOperatives, refresh } = useOperatives()

const workers = computed(() => 
  activeOperatives.value.filter(op => op.profile_slug !== 'lider')
)
</script>

<template>
  <OperativeWorkstation
    v-for="op in workers"
    :key="op.id"
    :operative="op"
  />
</template>

useKanban

useKanban

vue
<script setup>
const { tasks, tasksByStatus, refresh } = useKanban()

const columns = {
  todo: computed(() => tasksByStatus.value.todo),
  ready: computed(() => tasksByStatus.value.ready),
  running: computed(() => tasksByStatus.value.running),
  blocked: computed(() => tasksByStatus.value.blocked)
}
</script>

<template>
  <div class="kanban-board">
    <KanbanColumn
      v-for="(tasks, status) in columns"
      :key="status"
      :title="status"
      :tasks="tasks"
    />
  </div>
</template>
vue
<script setup>
const { tasks, tasksByStatus, refresh } = useKanban()

const columns = {
  todo: computed(() => tasksByStatus.value.todo),
  ready: computed(() => tasksByStatus.value.ready),
  running: computed(() => tasksByStatus.value.running),
  blocked: computed(() => tasksByStatus.value.blocked)
}
</script>

<template>
  <div class="kanban-board">
    <KanbanColumn
      v-for="(tasks, status) in columns"
      :key="status"
      :title="status"
      :tasks="tasks"
    />
  </div>
</template>

Common Patterns

常见模式

Hiring a New Operative

新增执行体

typescript
// POST /api/operatives/create
export default defineEventHandler(async (event) => {
  const { slug, callsign, cloneFrom } = await readBody(event)
  
  // Shell out to Hermes CLI
  const { stdout } = await execAsync(
    `hermes profile create ${slug} ${cloneFrom ? `--clone ${cloneFrom}` : ''}`
  )
  
  // Register in War Room DB
  const operative = await db.insert(operatives).values({
    profile_slug: slug,
    callsign: callsign || slug.toUpperCase(),
    avatar: randomAvatar(),
    color: randomColor(),
    active: true
  }).returning()
  
  return operative[0]
})
typescript
// POST /api/operatives/create
export default defineEventHandler(async (event) => {
  const { slug, callsign, cloneFrom } = await readBody(event)
  
  // 调用Hermes CLI
  const { stdout } = await execAsync(
    `hermes profile create ${slug} ${cloneFrom ? `--clone ${cloneFrom}` : ''}`
  )
  
  // 在War Room数据库中注册
  const operative = await db.insert(operatives).values({
    profile_slug: slug,
    callsign: callsign || slug.toUpperCase(),
    avatar: randomAvatar(),
    color: randomColor(),
    active: true
  }).returning()
  
  return operative[0]
})

Live Status Updates

实时状态更新

vue
<!-- components/OperativeWorkstation.vue -->
<script setup>
const props = defineProps(['operative'])
const { data: currentTask } = useFetch(`/api/operatives/${props.operative.id}/current-task`, {
  watch: true,
  refreshInterval: 2000 // Poll every 2s
})

const status = computed(() => {
  if (!currentTask.value) return 'Standing by'
  if (currentTask.value.status === 'running') return `Working on: ${currentTask.value.title}`
  if (currentTask.value.status === 'blocked') return 'Blocked'
  return 'Standing by'
})
</script>

<template>
  <div class="workstation" :style="{ borderColor: operative.color }">
    <img :src="operative.avatar" />
    <div class="status-pill">{{ status }}</div>
  </div>
</template>
vue
<!-- components/OperativeWorkstation.vue -->
<script setup>
const props = defineProps(['operative'])
const { data: currentTask } = useFetch(`/api/operatives/${props.operative.id}/current-task`, {
  watch: true,
  refreshInterval: 2000 // 每2秒轮询一次
})

const status = computed(() => {
  if (!currentTask.value) return '待命'
  if (currentTask.value.status === 'running') return `正在处理:${currentTask.value.title}`
  if (currentTask.value.status === 'blocked') return '阻塞'
  return '待命'
})
</script>

<template>
  <div class="workstation" :style="{ borderColor: operative.color }">
    <img :src="operative.avatar" />
    <div class="status-pill">{{ status }}</div>
  </div>
</template>

Delegation Chain Visualization

委派链可视化

vue
<script setup>
const props = defineProps(['taskId'])
const { data: chain } = useFetch(`/api/kanban/tasks/${props.taskId}/chain`)
</script>

<template>
  <div class="delegation-chain">
    <div v-for="task in chain" :key="task.id" class="chain-node">
      <OperativeBadge :operative="task.assignee" mini />
      <div class="task-info">
        <strong>{{ task.title }}</strong>
        <span class="status">{{ task.status }}</span>
      </div>
      <div v-if="task.children?.length" class="children">
        <DelegationChain
          v-for="child in task.children"
          :key="child.id"
          :task-id="child.id"
        />
      </div>
    </div>
  </div>
</template>
vue
<script setup>
const props = defineProps(['taskId'])
const { data: chain } = useFetch(`/api/kanban/tasks/${props.taskId}/chain`)
</script>

<template>
  <div class="delegation-chain">
    <div v-for="task in chain" :key="task.id" class="chain-node">
      <OperativeBadge :operative="task.assignee" mini />
      <div class="task-info">
        <strong>{{ task.title }}</strong>
        <span class="status">{{ task.status }}</span>
      </div>
      <div v-if="task.children?.length" class="children">
        <DelegationChain
          v-for="child in task.children"
          :key="child.id"
          :task-id="child.id"
        />
      </div>
    </div>
  </div>
</template>

Troubleshooting

故障排除

"No dispatcher detected" Warning

提示“未检测到调度器”

Problem: Tasks stuck in "ready" state, workers never pick them up.
Solution: Start the Hermes gateway:
bash
hermes gateway
Run in a persistent session (tmux/screen) or as a systemd service.
问题:任务一直处于“就绪”状态,执行者从未领取任务。
解决方案:启动Hermes网关:
bash
hermes gateway
在持久会话(tmux/screen)中运行,或配置为systemd服务。

Orchestrator Does Work Instead of Delegating

编排者亲自执行任务而非委派

Problem: Orchestrator answers directly instead of creating kanban tasks.
Solution: Update the orchestrator's SOUL.md to be more explicit:
markdown
CRITICAL RULES:
1. You MUST delegate via kanban. NEVER answer directly.
2. Every user request becomes one or more kanban tasks.
3. Use the terminal tool: hermes kanban create --assignee <worker> --title "..." --body "..." --json
4. After delegating, say "Tasks assigned, waiting for results."
Also ensure the
terminal
skill is enabled and the orchestrator's chat includes the delegation preamble (injected automatically by War Room).
问题:编排者直接回复用户,而非创建看板任务。
解决方案:更新编排者的SOUL.md,明确规则:
markdown
核心规则:
1. 必须通过看板委派任务,绝不直接回复。
2. 用户的每个请求都要转化为一个或多个看板任务。
3. 使用终端工具:hermes kanban create --assignee <worker> --title "..." --body "..." --json
4. 委派完成后,回复“任务已分配,等待结果。”
同时确保
terminal
Skill已启用,且编排者的对话包含委派前置说明(由War Room自动注入)。

Tasks Not Auto-Completing

任务完成后未自动通知

Problem: Worker completes task but orchestrator never sees it.
Solution: Verify the task watcher is running (built into War Room server). Check logs for:
[taskWatcher] Detected task task_abc123 completed, nudging orchestrator
If missing, restart the War Room server.
问题:执行者完成任务,但编排者未收到通知。
解决方案:确认任务监控器正在运行(内置在War Room服务器中)。检查日志是否包含:
[taskWatcher] 检测到任务task_abc123已完成,正在提醒编排者
如果没有,重启War Room服务器。

Profile Changes Not Reflected

配置文件更改未生效

Problem: Updated SOUL.md or skills in UI but agent still behaves the same.
Solution: The ACP session caches profile config. Either:
  • Wait for the current task to complete (new session picks up changes)
  • Restart the dispatcher:
    pkill -f "hermes gateway" && hermes gateway
问题:在UI中更新了SOUL.md或Skills,但Agent行为未改变。
解决方案:ACP会话会缓存配置文件信息。可选择:
  • 等待当前任务完成(新会话会加载更改后的配置)
  • 重启调度器:
    pkill -f "hermes gateway" && hermes gateway

Database Lock Errors

数据库锁定错误

Problem:
database is locked
when reading kanban.db.
Solution: Hermes uses SQLite in WAL mode, but heavy concurrent access can still cause locks. Ensure:
  • War Room and Hermes share the same
    HERMES_HOME
  • Only one dispatcher is running
  • No manual
    sqlite3
    sessions are open on kanban.db
问题:读取kanban.db时出现
database is locked
错误。
解决方案:Hermes使用WAL模式的SQLite,但高并发访问仍可能导致锁定。确保:
  • War Room和Hermes使用相同的
    HERMES_HOME
    路径
  • 仅运行一个调度器
  • 没有手动打开的
    sqlite3
    会话连接到kanban.db

Production Deployment

生产部署

Systemd Service

Systemd服务

ini
undefined
ini
undefined

/etc/systemd/system/hermes-war-room.service

/etc/systemd/system/hermes-war-room.service

[Unit] Description=Hermes War Room UI After=network.target
[Service] Type=simple User=hermes WorkingDirectory=/opt/hermes-war-room Environment="HERMES_HOME=/home/hermes/.hermes" Environment="NITRO_HOST=0.0.0.0" Environment="NITRO_PORT=3000" Environment="NODE_ENV=production" ExecStart=/usr/bin/node .output/server/index.mjs Restart=on-failure
[Install] WantedBy=multi-user.target

```bash
sudo systemctl enable hermes-war-room
sudo systemctl start hermes-war-room
[Unit] Description=Hermes War Room UI After=network.target
[Service] Type=simple User=hermes WorkingDirectory=/opt/hermes-war-room Environment="HERMES_HOME=/home/hermes/.hermes" Environment="NITRO_HOST=0.0.0.0" Environment="NITRO_PORT=3000" Environment="NODE_ENV=production" ExecStart=/usr/bin/node .output/server/index.mjs Restart=on-failure
[Install] WantedBy=multi-user.target

```bash
sudo systemctl enable hermes-war-room
sudo systemctl start hermes-war-room

Reverse Proxy (nginx)

反向代理(nginx)

nginx
server {
    listen 80;
    server_name war-room.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        # SSE requires no buffering
        proxy_buffering off;
        proxy_cache off;
    }
}
nginx
server {
    listen 80;
    server_name war-room.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        
        # SSE需要禁用缓冲
        proxy_buffering off;
        proxy_cache off;
    }
}

Environment-Specific Configs

环境特定配置

bash
undefined
bash
undefined

.env.production

.env.production

HERMES_HOME=/var/lib/hermes NITRO_HOST=127.0.0.1 NITRO_PORT=3000 WAR_ROOM_DB=/var/lib/hermes-war-room/war-room.db NODE_ENV=production
undefined
HERMES_HOME=/var/lib/hermes NITRO_HOST=127.0.0.1 NITRO_PORT=3000 WAR_ROOM_DB=/var/lib/hermes-war-room/war-room.db NODE_ENV=production
undefined

Key Files

核心文件结构

hermes-war-room/
├── server/
│   ├── api/
│   │   ├── missions/          # Mission CRUD + messaging
│   │   ├── operatives/        # Operative management + retrain
│   │   └── kanban/            # Kanban task queries + watch
│   ├── utils/
│   │   ├── db.ts              # War Room SQLite connection
│   │   ├── hermes.ts          # Hermes CLI wrappers
│   │   └── taskWatcher.ts     # Auto-nudge poller
│   └── middleware/
├── components/
│   ├── OperativeWorkstation.vue
│   ├── KanbanBoard.vue
│   ├── MissionChat.vue
│   └── OperativeDossier.vue
├── composables/
│   ├── useMission.ts
│   ├── useOperatives.ts
│   └── useKanban.ts
├── pages/
│   ├── index.vue              # War Room floor
│   ├── team.vue               # Roster/badges
│   └── missions.vue           # Archive
└── data/
    └── war-room.db            # War Room state (not Hermes kanban.db)
The War Room reads
~/.hermes/kanban.db
and
~/.hermes/profiles/
directly but never writes to them—writes go through the
hermes
CLI.
hermes-war-room/
├── server/
│   ├── api/
│   │   ├── missions/          # 任务协作的增删改查及消息功能
│   │   ├── operatives/        # 执行体管理及重新训练
│   │   └── kanban/            # 看板任务查询及监控
│   ├── utils/
│   │   ├── db.ts              # War Room SQLite连接
│   │   ├── hermes.ts          # Hermes CLI封装
│   │   └── taskWatcher.ts     # 自动提醒轮询器
│   └── middleware/
├── components/
│   ├── OperativeWorkstation.vue
│   ├── KanbanBoard.vue
│   ├── MissionChat.vue
│   └── OperativeDossier.vue
├── composables/
│   ├── useMission.ts
│   ├── useOperatives.ts
│   └── useKanban.ts
├── pages/
│   ├── index.vue              # 指挥中心主界面
│   ├── team.vue               # 执行体列表
│   └── missions.vue           # 任务协作归档
└── data/
    └── war-room.db            # War Room状态数据库(非Hermes的kanban.db)
War Room直接读取
~/.hermes/kanban.db
~/.hermes/profiles/
目录,但从不直接写入这些路径——所有写入操作都通过
hermes
CLI完成。