Loading...
Loading...
UI dashboard for visualizing and controlling Hermes multi-agent orchestration with kanban delegation
npx skill4agent add aradotso/hermes-skills hermes-war-room-uiSkill by ara.so — Hermes Skills collection.
~/.hermes/kanban.db~/.hermes/profiles/hermes# 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
# Run (requires Hermes CLI installed and Node 22+)
HERMES_HOME=$HOME/.hermes NITRO_HOST=127.0.0.1 NITRO_PORT=3000 \
node .output/server/index.mjshttp://localhost:3000git clone https://github.com/Naroh091/hermes-war-room.git
cd hermes-war-room
pnpm install
pnpm devhttp://localhost:3000# Required
HERMES_HOME=$HOME/.hermes # Path to Hermes profiles and kanban DB
NITRO_HOST=127.0.0.1 # Bind address
NITRO_PORT=3000 # Port
# Optional
WAR_ROOM_DB=./data/war-room.db # War Room's own SQLite database
NODE_ENV=production # Production/development mode<!-- Components displaying operatives -->
<OperativeWorkstation
:operative="operative"
:current-task="currentTask"
@click="openDossier"
/>~/.hermes/profiles/<slug>/<!-- 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<!-- 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 "..."// 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]
})// 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// 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"]
}// 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
}# Create orchestrator
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~/.hermes/profiles/lider/SOUL.md# 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
## Available Team
- **investigador**: Research, data gathering, fact-checking
- **legal**: Legal analysis, compliance review
- **writer**: Content creation, documentation
## 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
# 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>"
### 3. Configure Skills in War Room UI
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
### 4. Start the Dispatcher
The dispatcher claims `ready` tasks and hands them to workers:
```bash
# In a tmux/screen session
hermes gateway// 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..."
})// 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)<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><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><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>// 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]
})<!-- 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><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>hermes gatewayCRITICAL 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."terminal[taskWatcher] Detected task task_abc123 completed, nudging orchestratorpkill -f "hermes gateway" && hermes gatewaydatabase is lockedHERMES_HOMEsqlite3# /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.targetsudo systemctl enable hermes-war-room
sudo systemctl start hermes-war-roomserver {
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;
}
}# .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=productionhermes-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)~/.hermes/kanban.db~/.hermes/profiles/hermes