diff --git a/app/agents/[slug]/page.tsx b/app/agents/[slug]/page.tsx
new file mode 100644
index 0000000..2396416
--- /dev/null
+++ b/app/agents/[slug]/page.tsx
@@ -0,0 +1,271 @@
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { findAgentBySlug } from "@/lib/agents";
+import { formatDateTime } from "@/lib/utils";
+
+export const dynamic = "force-dynamic";
+
+function familyVariant(family: string) {
+ if (family === "zeroclaw") {
+ return "success";
+ }
+ if (family === "direct") {
+ return "warning";
+ }
+ return "default";
+}
+
+function dispatchVariant(state: string) {
+ return state === "failed" ? "warning" : state === "completed" ? "success" : "secondary";
+}
+
+export default async function AgentDetailPage({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await params;
+ const agent = await findAgentBySlug(slug);
+
+ if (!agent) {
+ notFound();
+ }
+
+ return (
+
+
+
+
+ Back to agents
+
+
+ {agent.emoji}
+ {agent.name}
+
+
{agent.role}
+
+
+ {agent.family}
+ {agent.status}
+ {agent.host}
+
+
+
+
+
+
+ Overview
+ Runtime, routing, heartbeat, and capability details for this agent.
+
+
+
+
+
- Host
+ - {agent.host}
+
+
+
- Model
+ - {agent.model || "Host-local/runtime-defined"}
+
+
+
- Dispatch
+ - {agent.defaultDispatchMethod}
+
+
+
- Heartbeat
+ - {agent.heartbeatAt ? formatDateTime(agent.heartbeatAt) : "No heartbeat"}
+
+
+
- Runtime Path
+ - {agent.runtimePath}
+
+ {agent.configPath ? (
+
+
- Config Path
+ - {agent.configPath}
+
+ ) : null}
+
+
- Current Task
+ - {agent.currentTask || "No heartbeat task"}
+
+
+
- Failure Count
+ - {agent.failureStreak}
+
+
+
+
+
+
Channels
+
+ {agent.channels.map((channel) => (
+
+ {channel.label}: {channel.value}
+
+ ))}
+
+
+
+
+
Tools
+
+ {agent.tools.length ? (
+ agent.tools.map((tool) => (
+
+ {tool}
+
+ ))
+ ) : (
+
No parsed tools.
+ )}
+
+
+
+
+
Capabilities
+
+ {agent.capabilities.length ? (
+ agent.capabilities.map((capability) => (
+
+ {capability}
+
+ ))
+ ) : (
+
No parsed capabilities.
+ )}
+
+
+
+
+
+
+
+
+ Activity Snapshot
+ Recent event and assignment health for this agent.
+
+
+
+
+
Active
+
{agent.activeTasks.length}
+
+
+
Completed
+
{agent.completedTasks.length}
+
+
+
+
+
Last Event
+ {agent.lastEvent ? (
+
+
+
+ {agent.lastEvent.event_type}
+
+
+ {agent.lastEvent.state || "n/a"}
+
+
+
{agent.lastEvent.summary}
+
+ {agent.lastEvent.detail || "No detail captured."}
+
+
+ {formatDateTime(agent.lastEvent.created_at)}
+
+
+ ) : (
+
No audit events recorded yet.
+ )}
+
+
+ {agent.notes.length ? (
+
+
Notes
+
+ {agent.notes.map((note) => (
+
+ {note}
+
+ ))}
+
+
+ ) : null}
+
+
+
+
+
+
+
+ Assigned Tasks
+ Current active work assigned to this agent.
+
+
+ {agent.activeTasks.length ? (
+ agent.activeTasks.map((task) => (
+
+
+
+
{task.title}
+
{task.description}
+
+
{task.dispatch_state}
+
+
+ {task.status}
+ {task.priority}
+ {task.tags.slice(0, 4).map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ ))
+ ) : (
+ No active tasks assigned.
+ )}
+
+
+
+
+
+ Recently Completed
+ Latest finished work attributed to this agent.
+
+
+ {agent.completedTasks.length ? (
+ agent.completedTasks.map((task) => (
+
+
+
+
{task.title}
+ {task.result_summary ? (
+
{task.result_summary}
+ ) : (
+
{task.description}
+ )}
+
+
done
+
+
+ {formatDateTime(task.completed_at || task.updated_at)}
+
+
+ ))
+ ) : (
+ No recently completed tasks.
+ )}
+
+
+
+
+ );
+}
diff --git a/app/api/agents/[slug]/route.ts b/app/api/agents/[slug]/route.ts
new file mode 100644
index 0000000..493b8b0
--- /dev/null
+++ b/app/api/agents/[slug]/route.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from "next/server";
+
+import { findAgentBySlug } from "@/lib/agents";
+
+export async function GET(
+ _request: Request,
+ { params }: { params: Promise<{ slug: string }> },
+) {
+ const { slug } = await params;
+ const agent = await findAgentBySlug(slug);
+ if (!agent) {
+ return NextResponse.json({ error: "agent_not_found" }, { status: 404 });
+ }
+
+ return NextResponse.json(agent);
+}
diff --git a/components/agents-client.tsx b/components/agents-client.tsx
index 8fe6f71..3b29821 100644
--- a/components/agents-client.tsx
+++ b/components/agents-client.tsx
@@ -1,5 +1,6 @@
"use client";
+import Link from "next/link";
import { useDeferredValue, useState } from "react";
import { Badge } from "@/components/ui/badge";
@@ -148,6 +149,25 @@ export function AgentsClient({
)}
+
+
Assigned Tasks
+ {agent.activeTasks.length ? (
+
+ {agent.activeTasks.slice(0, 3).map((task) => (
+
+
+
{task.title}
+
{task.status}
+
+
{task.result_summary || task.description}
+
+ ))}
+
+ ) : (
+
No assigned tasks.
+ )}
+
+
Tools
@@ -162,6 +182,13 @@ export function AgentsClient({
)}
+
+
+ View Agent Details
+
))}
diff --git a/lib/agents.ts b/lib/agents.ts
index 6c983de..34eb1f7 100644
--- a/lib/agents.ts
+++ b/lib/agents.ts
@@ -344,6 +344,11 @@ export async function findAgentByAssignmentKey(assignmentKey: string) {
return agents.find((agent) => agent.assignmentKey === assignmentKey || agent.aliases.includes(assignmentKey)) || null;
}
+export async function findAgentBySlug(slug: string) {
+ const agents = await listFleetAgents();
+ return agents.find((agent) => agent.slug === slug) || null;
+}
+
export async function listArchitecture() {
const agents = await listFleetAgents();
return {