diff --git a/README.md b/README.md
index b6c3576..b8de182 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,8 @@ It tracks and visualizes:
- dispatch lifecycle states and SQLite audit history
- OpenClaw swarm dispatch into `~/.clawdbot/active-tasks.json`
- ZeroClaw webhook dispatch for `grizzley` and `ice`
+- task callback API for remote completion/result sync
+- OpenClaw registry sync API for swarm task state reconciliation
- failure queue and dispatch history views
- family-specific runtime views for OpenClaw and ZeroClaw
- architecture documentation rendered directly from tracked config
diff --git a/app/api/sync/openclaw/route.ts b/app/api/sync/openclaw/route.ts
new file mode 100644
index 0000000..2613592
--- /dev/null
+++ b/app/api/sync/openclaw/route.ts
@@ -0,0 +1,7 @@
+import { NextResponse } from "next/server";
+
+import { syncOpenClawTasks } from "@/lib/openclaw-sync";
+
+export async function POST() {
+ return NextResponse.json(await syncOpenClawTasks());
+}
diff --git a/app/api/tasks/[id]/callback/route.ts b/app/api/tasks/[id]/callback/route.ts
new file mode 100644
index 0000000..653ce95
--- /dev/null
+++ b/app/api/tasks/[id]/callback/route.ts
@@ -0,0 +1,30 @@
+import { NextResponse } from "next/server";
+
+import { applyTaskCallback } from "@/lib/tasks";
+
+export async function POST(
+ request: Request,
+ { params }: { params: Promise<{ id: string }> },
+) {
+ const { id } = await params;
+ const numericId = Number(id);
+ if (!Number.isInteger(numericId) || numericId <= 0) {
+ return NextResponse.json({ error: "invalid_task_id" }, { status: 400 });
+ }
+
+ const payload = (await request.json()) as {
+ status?: "Backlog" | "Todo" | "In Progress" | "Review" | "Done";
+ dispatch_state?: "planned" | "assigned" | "dispatched" | "acknowledged" | "completed" | "failed";
+ summary?: string | null;
+ detail?: string | null;
+ completed_by?: string | null;
+ last_error?: string | null;
+ };
+
+ const updated = await applyTaskCallback(numericId, payload);
+ if (!updated) {
+ return NextResponse.json({ error: "task_not_found" }, { status: 404 });
+ }
+
+ return NextResponse.json(updated);
+}
diff --git a/app/api/tasks/[id]/route.ts b/app/api/tasks/[id]/route.ts
index 76e3f13..acef7d5 100644
--- a/app/api/tasks/[id]/route.ts
+++ b/app/api/tasks/[id]/route.ts
@@ -38,6 +38,18 @@ export async function PATCH(
reasoning_effort:
typeof payload.reasoning_effort === "string" ? payload.reasoning_effort : undefined,
model_hint: typeof payload.model_hint === "string" ? payload.model_hint : undefined,
+ result_summary:
+ payload.result_summary === null || typeof payload.result_summary === "string"
+ ? (payload.result_summary as never)
+ : undefined,
+ result_detail:
+ payload.result_detail === null || typeof payload.result_detail === "string"
+ ? (payload.result_detail as never)
+ : undefined,
+ completed_by:
+ payload.completed_by === null || typeof payload.completed_by === "string"
+ ? (payload.completed_by as never)
+ : undefined,
priority: payload.priority as never,
status: payload.status as never,
last_dispatch_at:
diff --git a/components/tasks-client.tsx b/components/tasks-client.tsx
index f9a1041..8992bae 100644
--- a/components/tasks-client.tsx
+++ b/components/tasks-client.tsx
@@ -347,6 +347,15 @@ export function TasksClient({
{task.target_channel || "n/a"}
+ {task.result_summary ? (
+
+
Latest Result
+
{task.result_summary}
+ {task.result_detail ? (
+
{task.result_detail}
+ ) : null}
+
+ ) : null}
{task.dispatch_state !== "dispatched" && task.dispatch_state !== "completed" ? (