diff --git a/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.test.tsx b/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.test.tsx
index 6e231517d..e13949074 100644
--- a/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.test.tsx
+++ b/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.test.tsx
@@ -21,6 +21,11 @@ vi.mock("@/components/layout/page-bar-context", async (importOriginal) => {
vi.mock("sonner", () => ({ toast: { error: vi.fn(), success: vi.fn(), info: vi.fn() } }));
vi.mock("@/components/engine/agent-roster-rail", () => ({ AgentRosterRail: () => null }));
vi.mock("@/lib/realtime/use-channel", () => ({ useChannel: vi.fn() }));
+// Stub the live "Now working" ticker: it fires its own fleet-queue fetch + setState on
+// every mount, which would add async churn (and stray fleet-queue calls) to each of the
+// dozens of EngineGoalsClient renders here. It has full coverage in its own test file
+// (now-working-ticker.test.tsx), so render it inert to keep this suite fast and isolated.
+vi.mock("@/components/engine/now-working-ticker", () => ({ NowWorkingTicker: () => null }));
const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof fetch;
diff --git a/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.tsx b/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.tsx
index 110d544d7..eec4c288d 100644
--- a/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.tsx
+++ b/src/app/(app)/o/[orgSlug]/p/[projectSlug]/goals/engine-goals-client.tsx
@@ -22,6 +22,7 @@ import type { FleetReadiness } from "@/lib/agent/project-fleet-readiness";
import { GoalCancelDialog } from "@/components/engine/goal-cancel-dialog";
import { GoalDeleteDialog } from "@/components/engine/goal-delete-dialog";
import { GoalEditDialog, type ReleaseOption } from "@/components/engine/goal-edit-dialog";
+import { NowWorkingTicker } from "@/components/engine/now-working-ticker";
import { usePageSection } from "@/hooks/use-page-section";
import { usePageBarRightSlot } from "@/components/layout/page-bar-context";
import {
@@ -512,7 +513,10 @@ export function EngineGoalsClient({
)}
-{/* Single-row toolbar: [View switcher] [Filters▾] [🔍] [Sort▾] [Discuss] [+ Add Goal] */}
+ {/* Live "Now working" strip — renders only while a machine is actively running a
+ goal in this project; null (no layout shift) otherwise. */}
+
+ {/* Single-row toolbar: [View switcher] [Filters▾] [🔍] [Sort▾] [Discuss] [+ Add Goal] */}
diff --git a/src/components/engine/now-working-ticker.test.tsx b/src/components/engine/now-working-ticker.test.tsx
new file mode 100644
index 000000000..aca2af483
--- /dev/null
+++ b/src/components/engine/now-working-ticker.test.tsx
@@ -0,0 +1,124 @@
+// @vitest-environment jsdom
+// Copyright (c) 2026 Interactor, Inc.
+// SPDX-License-Identifier: AGPL-3.0-or-later
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, waitFor } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import React from "react";
+
+// Capture the channel handler so a test can simulate a live `engine.changed` event.
+vi.mock("@/lib/realtime/use-channel", () => ({ useChannel: vi.fn() }));
+
+import { useChannel } from "@/lib/realtime/use-channel";
+import { NowWorkingTicker } from "./now-working-ticker";
+
+const PROJECT_ID = "proj-1";
+const USER_ID = "user-1";
+
+const mockFetch = vi.fn();
+global.fetch = mockFetch as unknown as typeof fetch;
+
+/** A fleet-queue goal row as the API returns it under `data.goals`. */
+function goal(over: Record
= {}) {
+ return {
+ goalId: "g1",
+ title: "Ship the cutover",
+ displayNumber: 7,
+ status: "running",
+ currentTask: { taskId: "t1", title: "Investigate the schema" },
+ ...over,
+ };
+}
+
+function respondWith(goals: unknown[]) {
+ mockFetch.mockResolvedValue({ ok: true, json: async () => ({ data: { goals } }) });
+}
+
+beforeEach(() => {
+ vi.clearAllMocks();
+});
+
+describe("NowWorkingTicker", () => {
+ it("renders nothing when no goals are running", async () => {
+ // A queued goal (not running) and a running goal with no currentTask are both excluded.
+ respondWith([
+ goal({ status: "queued", currentTask: null }),
+ goal({ goalId: "g2", status: "running", currentTask: null }),
+ ]);
+
+ render();
+
+ await waitFor(() => expect(mockFetch).toHaveBeenCalled());
+ expect(screen.queryByTestId("now-working-ticker")).not.toBeInTheDocument();
+ });
+
+ it("renders nothing when the fleet-queue fetch fails", async () => {
+ mockFetch.mockResolvedValue({ ok: false, json: async () => ({}) });
+
+ render();
+
+ await waitFor(() => expect(mockFetch).toHaveBeenCalled());
+ expect(screen.queryByTestId("now-working-ticker")).not.toBeInTheDocument();
+ });
+
+ it("renders the strip with the goal title and current task title when a goal is running", async () => {
+ respondWith([goal()]);
+
+ render();
+
+ const strip = await screen.findByTestId("now-working-ticker");
+ expect(strip).toHaveTextContent("Ship the cutover");
+ expect(strip).toHaveTextContent("Investigate the schema");
+ });
+
+ it("scopes the fetch to the project and shows a +N more badge for extra running goals", async () => {
+ respondWith([
+ goal(),
+ goal({ goalId: "g2", title: "Second goal", currentTask: { taskId: "t2", title: "Run the build" } }),
+ ]);
+
+ render();
+
+ const strip = await screen.findByTestId("now-working-ticker");
+ expect(strip).toHaveTextContent("+1 more");
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.stringContaining(`projectIds=${PROJECT_ID}`),
+ );
+ });
+
+ it("refetches when an engine.changed event fires on the project channel", async () => {
+ // First load: nothing running → no strip.
+ respondWith([]);
+ render();
+ await waitFor(() => expect(mockFetch).toHaveBeenCalled());
+ expect(screen.queryByTestId("now-working-ticker")).not.toBeInTheDocument();
+
+ // The first useChannel call is the project channel; grab its handler.
+ const handler = vi.mocked(useChannel).mock.calls[0]![1];
+
+ // A goal starts running; the next refetch surfaces it.
+ respondWith([goal()]);
+ handler({ type: "engine.changed", ids: { projectId: PROJECT_ID }, ts: "" });
+
+ const strip = await screen.findByTestId("now-working-ticker");
+ expect(strip).toHaveTextContent("Ship the cutover");
+ });
+
+ it("removes the strip when a refetch shows no goals are running", async () => {
+ // First load: a goal is running → the strip is visible.
+ respondWith([goal()]);
+ render();
+ await screen.findByTestId("now-working-ticker");
+
+ // The first useChannel call is the project channel; grab its handler.
+ const handler = vi.mocked(useChannel).mock.calls[0]![1];
+
+ // The run finishes; the next refetch returns nothing → the strip disappears.
+ respondWith([]);
+ handler({ type: "engine.changed", ids: { projectId: PROJECT_ID }, ts: "" });
+
+ await waitFor(() =>
+ expect(screen.queryByTestId("now-working-ticker")).not.toBeInTheDocument(),
+ );
+ });
+});
diff --git a/src/components/engine/now-working-ticker.tsx b/src/components/engine/now-working-ticker.tsx
new file mode 100644
index 000000000..99e1e8727
--- /dev/null
+++ b/src/components/engine/now-working-ticker.tsx
@@ -0,0 +1,100 @@
+// Copyright (c) 2026 Interactor, Inc.
+// SPDX-License-Identifier: AGPL-3.0-or-later
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { useChannel } from "@/lib/realtime/use-channel";
+
+// A thin "Now Working" activity strip pinned to the top of a project's Goals page. It
+// surfaces, per project, the goals a machine is ACTIVELY executing right now (status
+// `running` with a live `currentTask`) — the per-project complement to the account-wide
+// fleet sidebar, which can be collapsed and spans every project. It draws from the SAME
+// GOAL-grain fleet-queue read the sidebar uses (`/api/v1/me/engine/fleet-queue`), narrowed
+// to this project, so what it shows can't drift from what the fleet is doing. When nothing
+// is running it renders null (no strip, no layout shift) — a fetch failure or empty result
+// is indistinguishable from "nothing running", which is the correct, quiet default.
+
+/** One running goal + the task its machine is on — the subset of a fleet-queue row this
+ * strip needs. Mirrors `FleetGoalRow` (fleet-queue.ts) loosely so we don't import the
+ * server type into a client bundle. */
+interface RunningItem {
+ goalId: string;
+ title: string;
+ displayNumber: number | null;
+ status: string;
+ currentTask: { taskId: string; title: string } | null;
+}
+
+interface NowWorkingTickerProps {
+ /** The project's db id — scopes the fleet-queue read to this project. */
+ projectId: string;
+ /** The viewer's user id — used for the machine-heartbeat notifications channel. */
+ userId: string;
+}
+
+export function NowWorkingTicker({ projectId, userId }: NowWorkingTickerProps) {
+ const [runningItems, setRunningItems] = useState([]);
+
+ // Refetch the project-scoped fleet queue and keep only the goals a machine is actively
+ // running (status `running` AND a live `currentTask`). Best-effort: any failure (network,
+ // non-200, malformed body) clears to empty so the strip simply disappears — never a stale
+ // or error state, never a layout shift.
+ const refresh = useCallback(async () => {
+ try {
+ const res = await fetch(
+ `/api/v1/me/engine/fleet-queue?projectIds=${encodeURIComponent(projectId)}`,
+ );
+ if (!res.ok) {
+ setRunningItems([]);
+ return;
+ }
+ const json = (await res.json()) as { data?: { goals?: RunningItem[] } };
+ const goals = json.data?.goals ?? [];
+ setRunningItems(goals.filter((g) => g.status === "running" && g.currentTask));
+ } catch {
+ setRunningItems([]);
+ }
+ }, [projectId]);
+
+ useEffect(() => {
+ void refresh();
+ }, [refresh]);
+
+ // Live updates: a coarse `engine.changed` on the project channel covers goal/task state
+ // transitions (a run starting or finishing); the account notifications channel covers
+ // machine heartbeat changes that flip a machine online/offline mid-run. Either prompts a
+ // refetch so the strip tracks what's actually executing.
+ useChannel(`project:${projectId}`, (event) => {
+ if (event.type === "engine.changed") void refresh();
+ });
+ useChannel(`notifications:${userId}`, () => void refresh());
+
+ // Nothing running ⇒ render nothing (no strip, no reserved height).
+ if (runningItems.length === 0) return null;
+
+ const [first, ...rest] = runningItems;
+
+ return (
+
+
+
+
+
+ Now working
+
+ {first.title}
+ →
+ {first.currentTask?.title}
+
+ {rest.length > 0 && (
+
+ +{rest.length} more
+
+ )}
+
+ );
+}