Agent skill
dashboard
Real-time harness observability dashboard. Reads campaigns, fleet sessions, telemetry, and pending queues to present a snapshot of harness state at a glance. Invoked by /dashboard, /do status, or phrases like "what's happening" and "show activity".
Install this agent skill to your Project
npx add-skill https://github.com/SethGammon/Citadel/tree/main/skills/dashboard
SKILL.md
/dashboard — Harness Observability Dashboard
Identity
/dashboard reads the live state of the harness and presents it in a single, readable snapshot. No wall of JSON. No scrolling through log files. One command, one screen, full picture.
When to Use
- "What's happening?" / "Status?" / "What's going on?"
- "Show activity" / "Show me the dashboard"
- After returning to a project after time away
- When /do routes "status", "dashboard", "what's happening", "what's going on", "show activity"
- Directly:
/dashboard
Inputs
None required. Works with whatever state exists on disk.
Protocol
Step 1: COLLECT STATE
Read the following sources. Each is optional — if a file or directory doesn't exist, treat it as empty. Never crash on missing state.
Campaigns:
- Glob
.planning/campaigns/*.md - For each file, read the first 40 lines to extract:
Status:fieldDirection:field (truncate to 60 chars)- Phase progress (search for
Phase N of Mor## Phaseheadings) - Most recent line starting with
- [from the Decision Log
Cost Data (two sources, prefer real):
Source 1 -- Real token data (primary):
- Run
node scripts/session-tokens.js --todayandnode scripts/session-tokens.js --all - If the script exists and produces output, use its numbers (they read Claude Code's native session JSONL files for exact token counts and compute cost from API pricing).
Source 2 -- Session costs JSONL (fallback, also provides campaign attribution):
- Read
.planning/telemetry/session-costs.jsonl(if it exists) - For each line, parse as JSON
- Cost priority:
real_cost>override_cost>estimated_cost - Group by
campaign_slug. For each group:- Count sessions
- Sum best available cost
- Sum agents spawned and duration minutes
- Compute grand total across all campaigns
Live session:
- Read
.planning/telemetry/cost-tracker-state.jsonfor current session burn rate
Data source indicator:
- If
real_costfields are present in session-costs.jsonl entries, note "(real)" - If only
estimated_cost, note "(est)" so users know accuracy level
Fleet Sessions:
- Glob
.planning/fleet/session-*.md - For each file, read the first 30 lines to extract:
status:fieldwave:or wave numberagents:or agent count
Recent Telemetry:
- Read last 50 lines of
.planning/telemetry/hook-timing.jsonl(if it exists) - Read last 50 lines of
.planning/telemetry/audit.jsonl(if it exists) - Merge and sort by timestamp (descending). Take the 10 most recent entries.
- For each entry: extract
ts(ortimestamp),hook(orevent), and a short description field. Format as relative time.
Recent Hook Activity (separate from general telemetry):
- Read last 20 lines of
.planning/telemetry/hook-timing.jsonl - For each entry with
event: "timing", extract:hook— which hook fired (e.g.,post-edit,circuit-breaker)duration_ms— execution time in millisecondstimestamp— convert to relative timeoutcome— derive from context: ifduration_msis present and no matching error entry inhook-errors.jsonl, outcome ispass; if a block entry exists for the same hook within 1 second, outcome isblock
- For entries with
event: "counter"(fromincrement()), extract metric name as the "event" column with count context - This section makes silently-firing hooks visible without digging through raw files
Pending Queues:
- Count lines in
.planning/telemetry/doc-sync-queue.jsonl(or 0 if missing) - Count lines in
.planning/telemetry/merge-check-queue.jsonl(or 0 if missing) - Count files in
.planning/intake/(or 0 if missing)
Hook Value Data (for HOOKS VALUE section):
- Read
.planning/telemetry/hook-errors.jsonl(if it exists, last 200 lines)- Count entries where
hook= "protect-files" (blocked file access) - Count entries where
hook= "external-action-gate" (gated external actions) - Count entries where
hook= "quality-gate" (quality violations)
- Count entries where
- Read
.planning/telemetry/hook-timing.jsonl(if it exists, last 200 lines)- Count entries where
hook= "circuit-breaker" andmetric= "trips" - Count total entries from today (entries containing today's ISO date prefix)
- Count entries where
- Read
.planning/telemetry/audit.jsonl(if it exists, last 200 lines)- Count entries mentioning "circuit-breaker" or "circuit_breaker"
Health:
- Count circuit breaker entries from audit.jsonl (from hook value data above)
- Count total lines in
.planning/telemetry/audit.jsonlwritten today - Count entries in
hooksarray of.claude/hooks-template.json(or.claude/hooks.jsonif template not present); use 0 if neither exists - Read
.claude/harness.json→trustobject:sessions_completed,campaigns_completedcounters- Compute level: novice (sessions < 5), familiar (5-19), trusted (20+ with 2+ campaigns)
- If
trust.overrideis set, use that and note "(override)"
Step 2: FORMAT RELATIVE TIMESTAMPS
Convert ISO timestamps to human-readable relative time:
- < 60 seconds ago: "just now"
- < 60 minutes ago: "{N} min ago"
- < 24 hours ago: "{N} hr ago"
-
= 24 hours ago: "{N} days ago"
If a timestamp is unparseable, display it as-is without crashing.
Step 3: RENDER DASHBOARD
Output the following format verbatim, substituting real values. Omit sections that are entirely empty only if explicitly noted below. Always show the section header even when the content is "(none active)".
=== Citadel Dashboard ===
As of: {relative timestamp of most recent event, or "now"}
CAMPAIGNS
{slug}: Phase {N}/{total} — {direction, max 60 chars, ellipsis if truncated}
Last event: {most recent telemetry entry for this campaign, or "no telemetry"}
(none active)
COSTS
This session: ${cost} | {duration} min | ${rate}/min | {messages} msgs | {agents} agents
Today: ${today_total} across {today_sessions} sessions
All time: ${all_time_total} across {all_time_sessions} sessions ({data_source})
By campaign:
{slug}: ${total_cost} across {sessions} sessions ({agents} agents, {minutes} min)
_unattached: ${total_cost} across {sessions} sessions
(no cost data recorded yet)
HOOKS VALUE
Circuit breaker: {N} trips (prevented token spirals)
Quality gate: {N} violations caught pre-commit
Protect-files: {N} blocks (path traversal, secrets)
External gate: {N} actions gated
Total hook fires today: {N}
(raw facts only -- no inflated savings claims)
FLEET SESSIONS
{slug}: Wave {N} — {agent count} agents — {status}
(none active)
RECENT ACTIVITY (last 10 events)
{relative time} | {hook/event name} | {description}
(no telemetry recorded yet)
HOOK ACTIVITY (last 10 hook fires)
{relative time} | {hook name} | {duration_ms}ms | {outcome: pass/block/warn}
(no hook timing recorded yet — set CITADEL_DEBUG=true in settings.json for verbose output)
PENDING
Doc sync: {N} items queued
Merge reviews: {N} items queued
Intake items: {N} in .planning/intake/
HEALTH
Circuit breaker trips this session: {N}
Audit entries today: {N}
Hooks installed: {N}
Trust level: {novice | familiar | trusted} ({N} sessions, {N} campaigns)
QUICK COMMANDS
/do continue — resume active campaign
/do rollback — restore last checkpoint
/telemetry — cost breakdown, hook activity, telemetry settings
/triage prs — review open PRs
/pr-watch — watch PR CI
/learn — extract patterns from last completed campaign
Step 4: FRINGE CASE HANDLING
If .planning/ does not exist: Show the dashboard with all counts as 0 and all lists as "(none active)" or "(no telemetry recorded yet)". Add a note below the dashboard:
NOTE: .planning/ not found. Run /do setup to initialize harness state.
If harness.json is missing or malformed: Show "not configured" for hooks count. Do not crash.
If a campaign file is malformed markdown:
Skip that file. Log (1 campaign file skipped — malformed) in the CAMPAIGNS
section if any were skipped.
If telemetry files are very large: Only read the last 50 lines of each telemetry file. This caps read cost regardless of file size. Note: "Showing last 50 events per log file."
If timestamps are missing from telemetry entries: Use the file's modification time as a fallback. If that's also unavailable, display the entry without a timestamp.
Quality Gates
- Dashboard must render even when all state files are missing
- Never display raw JSON to the user — always parse and format
- Relative timestamps required — never show raw ISO strings in output
- Campaign direction truncated to 60 chars with "..." if longer
- Total output must be skimmable in under 30 seconds
Exit Protocol
/dashboard does not produce a HANDOFF block. It is a read-only observability tool. After displaying the dashboard, wait for the next user command.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
triage
GitHub issue and PR investigator. Pulls open issues/PRs, classifies them, searches the codebase for root cause or reviews contributed code, proposes fixes with file:line references, and optionally implements fixes. Handles both issues and pull requests.
infra-audit
Reads docker-compose, env files, ORM configs, and connection strings to map current infrastructure. Flags missing layers (cache, queue, analytics) based on observed access patterns. Outputs a structured infrastructure manifest.
fleet
Parallel campaign orchestrator. Runs multiple campaigns in coordinated waves within a single session. Spawns 2-3 agents per wave in isolated worktrees, collects discoveries, shares context between waves. Use when work decomposes into 3+ independent streams that can run simultaneously.
design
Generates and maintains a design manifest for visual consistency. In existing projects, reads current styles and documents the design language. In new projects, asks a few questions and generates a starter manifest. The post-edit hook reads the manifest and flags deviations.
test-gen
Generate and verify tests — happy path, edge cases, error paths — using the project's own framework and patterns
workspace
Multi-repo campaign coordinator. Same lifecycle as fleet -- scope claims, discovery relay, wave-based execution -- but the unit of work is a repo, not a file. Coordinates campaigns across repositories with shared context.
Didn't find tool you were looking for?