diff --git a/packages/smithers/src/cli/ControlCommands.ts b/packages/smithers/src/cli/ControlCommands.ts
index efce67e6a5..1a622e4bf9 100644
--- a/packages/smithers/src/cli/ControlCommands.ts
+++ b/packages/smithers/src/cli/ControlCommands.ts
@@ -30,6 +30,7 @@ import * as Project from "../Project.ts"
 import * as Bridge from "./ControlBridge.ts"
 import { prepareHistoryRun, reconcileHistory } from "./HistoryCommands.ts"
 import * as Presentation from "./Presentation.ts"
+import * as RunActivity from "./RunActivity.ts"
 import * as RunProgress from "./RunProgress.ts"
 import * as TargetApprovals from "./TargetApprovals.ts"
 
@@ -356,7 +357,15 @@ export const createRunsCli = (runtime: Bridge.Runtime = {}) =>
                   cursor: c.options.cursor
                 })
                 const listed = yield* (yield* Control.Control).list(listing)
-                return yield* RunListing.label(listed, yield* Clock.currentTimeMillis)
+                const labelled = yield* RunListing.label(listed, yield* Clock.currentTimeMillis)
+                return labelled._tag !== "runs" ? labelled : {
+                  ...labelled,
+                  items: labelled.items.map((run) =>
+                    run.executionView === undefined
+                      ? run
+                      : { ...run, executionView: RunActivity.presentView(run.executionView, run.flowId) }
+                  )
+                }
               }),
               c.options,
               runtime
@@ -401,7 +410,7 @@ export const createRunsCli = (runtime: Bridge.Runtime = {}) =>
                   operation: "run diagnosis",
                   subject: run.runId
                 })
-                return { ...run, diagnosis: Forensics.digest(events, run.runId) }
+                return RunActivity.show(run, events)
               }),
               c.options,
               runtime
@@ -514,7 +523,7 @@ export const createRunsCli = (runtime: Bridge.Runtime = {}) =>
             message: String(Redaction.redactDiagnostic(Failure.operatorSentence(cause)))
           })
         } finally {
-          renderer?.close("ended")
+          renderer?.close(c.options.follow ? "ended" : "caught-up")
         }
       }
     })
diff --git a/packages/smithers/src/cli/RunActivity.ts b/packages/smithers/src/cli/RunActivity.ts
new file mode 100644
index 0000000000..4eb65b4417
--- /dev/null
+++ b/packages/smithers/src/cli/RunActivity.ts
@@ -0,0 +1,284 @@
+/**
+ * What a run is doing, folded from the events `runs show` already reads: the
+ * executions its flow spawned, the actions each one is running, and when the
+ * run last made progress.
+ *
+ * The engine starts every planned run as an `agent/run` execution, which runs
+ * the approved plan through a `registry/entry/<digest>/<flow>` execution. Both
+ * are wrappers around the operator's flow, so this view names the run by its
+ * flow and folds the wrappers' work into that one row.
+ *
+ * @since 1.0.0
+ */
+
+import type { ControlSchema } from "@smthrs/control"
+import * as Forensics from "../Forensics.ts"
+
+/** The engine flow that executes an approved plan. */
+const planWrapper = "agent/run"
+/** The registry's entry execution: `registry/entry/<digest>/<flow>`. */
+const entryWrapper = /^registry\/entry\/[0-9a-f]+\/(.+)$/
+
+/**
+ * Rows `runs show` prints before it reports the rest as omitted.
+ *
+ * @category constants
+ * @since 1.0.0
+ */
+export const maximumExecutions = 20
+/** Running actions printed for one execution. */
+const maximumRunning = 3
+
+const terminal: ReadonlySet<string> = new Set(["completed", "failed", "cancelled"])
+
+/**
+ * Event kinds that observe a run without advancing it: the status monitor's
+ * periodic check and the gateway's watch keepalive.
+ */
+const observational: ReadonlySet<string> = new Set(["control.status.observed", "control.gateway.heartbeat"])
+
+/**
+ * One execution in a run's tree, as `runs show` prints it.
+ *
+ * @category models
+ * @since 1.0.0
+ */
+export interface Execution {
+  readonly executionId: string
+  readonly flowName: string
+  readonly status: string
+  /** The execution that spawned this one; the run's id for the run's own row. */
+  readonly parent: string | null
+  readonly round: number
+  readonly startedAtMs: number | null
+  readonly finishedAtMs: number | null
+  /** Actions scheduled and not yet settled, newest last; null when none is. */
+  readonly running: string | null
+}
+
+/**
+ * The activity `runs show` adds to a run.
+ *
+ * @category models
+ * @since 1.0.0
+ */
+export interface Activity {
+  /** When the run last recorded progress; monitor checks do not count. */
+  readonly lastProgressAt: number | undefined
+  /** The run's own row first, then live executions, then the latest settled ones. */
+  readonly executions: ReadonlyArray<Execution>
+  /** Executions beyond {@link maximumExecutions}, all of them settled. */
+  readonly omitted: number
+}
+
+const record = (value: unknown): Record<string, unknown> =>
+  value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {}
+
+const text = (value: unknown): string | undefined => typeof value === "string" && value !== "" ? value : undefined
+
+const number = (value: unknown): number | null => typeof value === "number" && Number.isFinite(value) ? value : null
+
+/**
+ * The name an operator gave the flow an engine execution runs.
+ *
+ * @param flowName the execution's engine flow name
+ * @param runFlow the flow the run was started from
+ * @category rendering
+ * @since 1.0.0
+ */
+export const flowNameOf = (flowName: string, runFlow: string): string =>
+  flowName === planWrapper ? runFlow : entryWrapper.exec(flowName)?.[1] ?? flowName
+
+/**
+ * A run's execution view with the plan wrapper named by the run's flow.
+ *
+ * @category rendering
+ * @since 1.0.0
+ */
+export const presentView = <
+  View extends { readonly root: { readonly flowName: string }; readonly current: { readonly flowName: string } }
+>(
+  view: View | undefined,
+  runFlow: string
+): View | undefined =>
+  view === undefined ? undefined : {
+    ...view,
+    root: { ...view.root, flowName: flowNameOf(view.root.flowName, runFlow) },
+    current: { ...view.current, flowName: flowNameOf(view.current.flowName, runFlow) }
+  }
+
+interface Folded {
+  generation: number
+  sequence: number
+  flowName: string
+  status: string
+  parent: string | null
+  round: number
+  startedAtMs: number | null
+  finishedAtMs: number | null
+}
+
+/**
+ * Folds a run's events into its execution tree and latest progress.
+ *
+ * @param events the run's journal, in order
+ * @param run the run's id and the flow it was started from
+ * @category constructors
+ * @since 1.0.0
+ */
+export const fold = (
+  events: ReadonlyArray<ControlSchema.ControlEvent>,
+  run: { readonly runId: string; readonly flowId: string }
+): Activity => {
+  let lastProgressAt: number | undefined
+  const executions = new Map<string, Folded>()
+  // Scheduled and unsettled ActionCall nodes per execution, by node id.
+  const running = new Map<string, Map<string, string>>()
+  for (const event of events) {
+    if (!observational.has(event.kind)) {
+      lastProgressAt = lastProgressAt === undefined ? event.occurredAt : Math.max(lastProgressAt, event.occurredAt)
+    }
+    if (event.kind !== "control.engine.event") continue
+    const envelope = record(event.payload)
+    const executionId = text(envelope.executionId)
+    if (executionId === undefined) continue
+    const payload = record(envelope.payload)
+    const nodeId = text(payload.nodeId)
+    if (envelope.eventType === "flows.engine.node-scheduled" && nodeId !== undefined) {
+      if (payload.kind !== "ActionCall") continue
+      const nodes = running.get(executionId) ?? new Map<string, string>()
+      nodes.set(nodeId, text(payload.action) ?? nodeId)
+      running.set(executionId, nodes)
+      continue
+    }
+    if (envelope.eventType === "flows.engine.node-settled" && nodeId !== undefined) {
+      running.get(executionId)?.delete(nodeId)
+      continue
+    }
+    const observation = record(record(payload.executionFact).observation)
+    const observed = text(observation.executionId)
+    const flowName = text(observation.flowName)
+    const status = text(observation.status)
+    if (observed !== executionId || flowName === undefined || status === undefined) continue
+    const generation = number(envelope.generation) ?? 0
+    const sequence = number(envelope.sequence) ?? 0
+    const previous = executions.get(executionId)
+    if (
+      previous !== undefined &&
+      (generation < previous.generation || generation === previous.generation && sequence < previous.sequence)
+    ) continue
+    executions.set(executionId, {
+      generation,
+      sequence,
+      flowName,
+      status,
+      parent: text(observation.parentRunId) ?? null,
+      round: number(observation.roundOrdinal) ?? 0,
+      startedAtMs: number(observation.startedAtMs),
+      finishedAtMs: number(observation.finishedAtMs)
+    })
+  }
+  // The wrappers are the run itself: their ids resolve to the run's row.
+  const wrappers = new Set(
+    [...executions].filter(([id, fact]) =>
+      id === run.runId || fact.flowName === planWrapper || entryWrapper.test(fact.flowName)
+    ).map(([id]) => id)
+  )
+  const actions = (ids: Iterable<string>): string | null => {
+    const names = [...ids].flatMap((id) => [...(running.get(id)?.values() ?? [])])
+    if (names.length === 0) return null
+    const shown = names.slice(-maximumRunning).join(" · ")
+    return names.length > maximumRunning ? `${shown} · ${names.length - maximumRunning} more` : shown
+  }
+  const own = executions.get(run.runId)
+  const head: Execution | undefined = own === undefined ? undefined : {
+    executionId: run.runId,
+    flowName: run.flowId,
+    status: own.status,
+    parent: null,
+    round: own.round,
+    startedAtMs: own.startedAtMs,
+    finishedAtMs: own.finishedAtMs,
+    running: terminal.has(own.status) ? null : actions(wrappers)
+  }
+  const children: Array<Execution> = []
+  for (const [executionId, fact] of executions) {
+    if (wrappers.has(executionId)) continue
+    children.push({
+      executionId,
+      flowName: flowNameOf(fact.flowName, run.flowId),
+      status: fact.status,
+      parent: fact.parent === null || wrappers.has(fact.parent) ? run.runId : fact.parent,
+      round: fact.round,
+      startedAtMs: fact.startedAtMs,
+      finishedAtMs: fact.finishedAtMs,
+      running: terminal.has(fact.status) ? null : actions([executionId])
+    })
+  }
+  const live = children.filter((row) => !terminal.has(row.status))
+  const settled = children.filter((row) => terminal.has(row.status))
+    .sort((left, right) => (right.finishedAtMs ?? 0) - (left.finishedAtMs ?? 0))
+  const ordered = [...(head === undefined ? [] : [head]), ...live, ...settled]
+  const shown = ordered.slice(0, Math.max(maximumExecutions, (head === undefined ? 0 : 1) + live.length))
+  return {
+    lastProgressAt,
+    executions: shown,
+    omitted: ordered.length - shown.length
+  }
+}
+
+/**
+ * What a recorded code drift means for resuming the run.
+ *
+ * @category rendering
+ * @since 1.0.0
+ */
+export const driftVerdict = (drift: NonNullable<ControlSchema.RunSummary["codeDrift"]>): string =>
+  drift.recorded !== undefined && drift.current === undefined
+    ? "flow is no longer on disk; the run cannot resume"
+    : drift.recorded !== undefined
+    ? "flow changed since the run started; resume needs --allow-code-drift"
+    : "engine changed since the run started; resume needs --allow-code-drift"
+
+/**
+ * A run as `runs show` prints it.
+ *
+ * @category models
+ * @since 1.0.0
+ */
+export type Shown = Omit<ControlSchema.RunSummary, "codeDrift"> & {
+  readonly codeDrift?: NonNullable<ControlSchema.RunSummary["codeDrift"]> & { readonly verdict: string }
+  readonly executions: ReadonlyArray<Execution>
+  readonly executionsOmitted?: number
+  readonly diagnosis: Forensics.Digest
+}
+
+/**
+ * The run `runs show` prints: its flow named in place of the engine wrapper,
+ * its last progress as `updatedAt`, its executions, its drift verdict, and a
+ * diagnosis whose `endedAt` is absent until the run settles.
+ *
+ * @param run the observed run
+ * @param events the run's journal, in order
+ * @category rendering
+ * @since 1.0.0
+ */
+export const show = (
+  run: ControlSchema.RunSummary,
+  events: ReadonlyArray<ControlSchema.ControlEvent>
+): Shown => {
+  const activity = fold(events, run)
+  const diagnosis = Forensics.digest(events, run.runId)
+  const { codeDrift, ...recorded } = run
+  return {
+    ...recorded,
+    ...(run.executionView === undefined ? {} : { executionView: presentView(run.executionView, run.flowId) }),
+    updatedAt: Math.max(run.updatedAt, activity.lastProgressAt ?? run.updatedAt),
+    ...(codeDrift === undefined ? {} : { codeDrift: { ...codeDrift, verdict: driftVerdict(codeDrift) } }),
+    executions: activity.executions,
+    ...(activity.omitted === 0 ? {} : { executionsOmitted: activity.omitted }),
+    // The fold's span ends at the last event it reads, which is not an end
+    // while the run is live.
+    diagnosis: terminal.has(run.status) ? diagnosis : { ...diagnosis, endedAt: undefined }
+  }
+}
diff --git a/packages/smithers/src/cli/RunProgress.ts b/packages/smithers/src/cli/RunProgress.ts
index 954cdd2a71..0b80f1bb43 100644
--- a/packages/smithers/src/cli/RunProgress.ts
+++ b/packages/smithers/src/cli/RunProgress.ts
@@ -15,6 +15,7 @@ import { Cause, Context, Effect, Exit, HashMap, HashSet, Stream } from "effect"
 import type { Writable } from "node:stream"
 import { stripVTControlCharacters } from "node:util"
 import { causeLine } from "../internal/Failure.ts"
+import { flowNameOf } from "./RunActivity.ts"
 
 /**
  * Invocation-scoped display settings, independent of document encoding.
@@ -62,6 +63,11 @@ export interface State {
   /** Native facts can correct an earlier telemetry result without counting twice. */
   readonly nativeCalls: HashSet.HashSet<string>
   readonly callFailures: HashMap.HashMap<string, boolean>
+  /** Engine actions scheduled and not yet settled, by execution and node. */
+  readonly engineNodes: HashSet.HashSet<string>
+  /** Spawned executions already reported started, and those reported settled. */
+  readonly executionsStarted: HashSet.HashSet<string>
+  readonly executionsSettled: HashSet.HashSet<string>
   readonly status: string
   readonly settled: boolean
 }
@@ -95,6 +101,9 @@ export const initial = (): State => ({
   reportedCalls: HashSet.empty(),
   nativeCalls: HashSet.empty(),
   callFailures: HashMap.empty(),
+  engineNodes: HashSet.empty(),
+  executionsStarted: HashSet.empty(),
+  executionsSettled: HashSet.empty(),
   status: "Connecting to run",
   settled: false
 })
@@ -135,6 +144,100 @@ const logLines = (value: unknown): ReadonlyArray<Line> => {
     : shown
 }
 
+const settledExecution: ReadonlySet<string> = new Set(["completed", "failed", "cancelled"])
+
+/**
+ * A native engine record a run's journal carries in a `control.engine.event`
+ * envelope: the actions it schedules and settles and the executions it spawns.
+ * The run's own execution and the engine wrappers around its flow are the run
+ * itself, so only the executions it spawned are reported.
+ */
+const projectEngine = (
+  state: State,
+  event: ControlSchema.ControlEvent
+): { readonly state: State; readonly lines: ReadonlyArray<Line> } => {
+  const envelope = record(event.payload)
+  const payload = record(envelope.payload)
+  const executionId = typeof envelope.executionId === "string" ? envelope.executionId : ""
+  const nodeId = typeof payload["nodeId"] === "string" ? payload["nodeId"] : ""
+  const key = `${executionId}\u0000${nodeId}`
+  switch (envelope.eventType) {
+    case "flows.engine.node-scheduled": {
+      if (payload["kind"] !== "ActionCall" || nodeId === "") return { state, lines: [] }
+      const name = text(payload["action"], 100) || text(nodeId, 100)
+      const retry = HashSet.has(state.engineNodes, key)
+      return {
+        state: {
+          ...state,
+          started: state.started + (retry ? 0 : 1),
+          active: retry ? state.active : [...state.active, name].slice(-8),
+          activeCallIds: retry ? state.activeCallIds : [...state.activeCallIds, key].slice(-8),
+          engineNodes: HashSet.add(state.engineNodes, key),
+          status: name,
+          settled: false
+        },
+        lines: [{ level: "step", text: `${retry ? "Retrying" : "Running"} ${name}` }]
+      }
+    }
+    case "flows.engine.node-settled": {
+      if (!HashSet.has(state.engineNodes, key)) return { state, lines: [] }
+      const name = text(payload["action"], 100) || text(nodeId, 100)
+      const outcome = payload["outcome"]
+      const failed = outcome === "failure" || outcome === "failed"
+      const skipped = outcome === "skipped" || outcome === "deferred"
+      const index = state.activeCallIds.indexOf(key)
+      const message = failed ? text(payload["message"]) : ""
+      return {
+        state: {
+          ...state,
+          completed: state.completed + (failed || skipped ? 0 : 1),
+          failed: state.failed + (failed ? 1 : 0),
+          skipped: state.skipped + (skipped ? 1 : 0),
+          skippedStarted: state.skippedStarted + (skipped ? 1 : 0),
+          active: index < 0 ? state.active : state.active.filter((_, position) => position !== index),
+          activeCallIds: index < 0
+            ? state.activeCallIds
+            : state.activeCallIds.filter((_, position) => position !== index),
+          engineNodes: HashSet.remove(state.engineNodes, key),
+          status: "Working",
+          settled: false
+        },
+        lines: [{
+          level: failed ? "error" : skipped ? "warn" : "success",
+          text: `${name}${failed ? " failed" : skipped ? " skipped" : outcome === "clean" ? " cached" : " completed"}${
+            message === "" ? "" : ` · ${message}`
+          }`
+        }]
+      }
+    }
+    default: {
+      const observation = record(record(payload["executionFact"])["observation"])
+      const flowName = typeof observation["flowName"] === "string" ? observation["flowName"] : ""
+      const status = typeof observation["status"] === "string" ? observation["status"] : ""
+      if (
+        observation["executionId"] !== executionId || executionId === event.runId || flowName === "" ||
+        flowNameOf(flowName, "") !== flowName
+      ) return { state, lines: [] }
+      const name = `${text(flowName, 100)} · ${text(executionId, 48)}`
+      if (settledExecution.has(status)) {
+        if (HashSet.has(state.executionsSettled, executionId)) return { state, lines: [] }
+        return {
+          state: { ...state, executionsSettled: HashSet.add(state.executionsSettled, executionId) },
+          lines: [{
+            level: status === "completed" ? "success" : status === "failed" ? "error" : "warn",
+            text: `${name} ${status}`
+          }]
+        }
+      }
+      if (HashSet.has(state.executionsStarted, executionId)) return { state, lines: [] }
+      return {
+        state: { ...state, executionsStarted: HashSet.add(state.executionsStarted, executionId) },
+        lines: [{ level: "info", text: `Started ${name}` }]
+      }
+    }
+  }
+}
+
 /**
  * Projects only deliberate lifecycle/log fields, never arbitrary inputs,
  * generated code, model reasoning, or complete task result payloads.
@@ -147,6 +250,7 @@ export const project = (
   event: ControlSchema.ControlEvent
 ): { readonly state: State; readonly lines: ReadonlyArray<Line> } => {
   const native = nativeCallEvent(event)
+  if (native === undefined && event.kind === "control.engine.event") return projectEngine(state, event)
   event = native ?? event
   const payload = record(event.payload)
   const callKey = callEventKey(event)
@@ -311,7 +415,11 @@ const quote = (value: string): string => `'${value.replace(/'/g, "'\\''")}'`
  */
 export interface Renderer {
   readonly event: (event: ControlSchema.ControlEvent) => void
-  readonly close: (reason: "ended" | "interrupted" | "failed") => void
+  /**
+   * `caught-up` is a read of the recorded events that stops at the newest one
+   * without following the run.
+   */
+  readonly close: (reason: "ended" | "caught-up" | "interrupted" | "failed") => void
 }
 
 /**
@@ -422,13 +530,18 @@ export const make = (runId: string, options: Options): Renderer => {
           ? "Stopped watching · run status is retained"
           : reason === "failed"
           ? "Progress stream failed · inspect the saved run"
+          : reason === "caught-up"
+          ? `End of recorded events · ${summary(state)}`
           : "Stopped watching before settlement"
         if (lastSettlement !== result) conclude(result, reason === "failed")
+        const id = quote(text(runId, 100))
         const next = state.status === "Waiting for approval"
-          ? `smthrs approvals list\nsmthrs runs logs ${quote(text(runId, 100))}`
+          ? `smthrs approvals list\nsmthrs runs logs ${id}`
           : state.status === "Completed"
-          ? `smthrs runs output ${quote(text(runId, 100))}`
-          : `smthrs runs show ${quote(text(runId, 100))}\nsmthrs runs logs ${quote(text(runId, 100))}`
+          ? `smthrs runs output ${id}`
+          : reason === "caught-up" && !state.settled
+          ? `smthrs runs logs ${id} --follow\nsmthrs runs show ${id}`
+          : `smthrs runs show ${id}\nsmthrs runs logs ${id}`
         if (live) clack.note(next, "Next", common)
         else output.write(`${next}\n`)
       })
diff --git a/packages/smithers/src/internal/NativeControl.ts b/packages/smithers/src/internal/NativeControl.ts
index 463eb1333c..ee42ae333a 100644
--- a/packages/smithers/src/internal/NativeControl.ts
+++ b/packages/smithers/src/internal/NativeControl.ts
@@ -1819,11 +1819,22 @@ export const make = (
     const stateRoot = config.stateRoot ?? config.root ?? process.cwd()
     const stores = Layer.mergeAll(SqlJournal.layer({ capacity: 1024, overflow: "reject" }), Layer.fresh(RunStore.layer))
       .pipe(Layer.provideMerge(native.observe(config.databases?.control ?? databasePath(stateRoot))), Layer.orDie)
-    const runtime = SqlControlRuntime.layer({
-      approvalAuthority: config.approvalAuthority ?? ApprovalAuthority.local,
-      principal: config.principal,
-      engineVersion: packageVersion
-    }).pipe(Layer.provide([stores, native.crypto]), Layer.orDie)
+    const root = config.root ?? process.cwd()
+    // The drift a reader reports is read against the flows on disk, as the
+    // writer's own check reads them. With no flows here every live run of an
+    // authored flow read as drifted, with its recorded digest and no current one.
+    const runtime = Layer.unwrap(Effect.gen(function*() {
+      const discovered = yield* Registry.Registry
+      return SqlControlRuntime.layer({
+        approvalAuthority: config.approvalAuthority ?? ApprovalAuthority.local,
+        principal: config.principal,
+        engineVersion: packageVersion,
+        currentFlows: () =>
+          discovered.list().pipe(
+            Effect.map((flows) => [...systemFlows, ...flows.map((flow) => durableFlow(flow, root, emptyHost()))])
+          )
+      })
+    })).pipe(Layer.provide([stores, native.crypto, registry]), Layer.orDie)
     const engineFile = config.databases?.engine ?? executionDatabasePath(stateRoot)
     const executor = Layer.effect(ControlExecutor.ControlExecutor)(Effect.gen(function*() {
       const engine = DatabaseLocation.exists(engineFile)
diff --git a/packages/smithers/vitest.config.ts b/packages/smithers/vitest.config.ts
index e85322bbe8..8b2350d9b8 100644
--- a/packages/smithers/vitest.config.ts
+++ b/packages/smithers/vitest.config.ts
@@ -25,7 +25,15 @@ export default defineConfig({
     // compile cache, keyed by source content, lets every start after the
     // first reuse that work. Spawned children inherit it from this
     // environment; nothing a command prints depends on it.
-    env: { NODE_COMPILE_CACHE: join(tmpdir(), "smithers-cli-test-compile-cache") },
+    //
+    // Incur appends "Skills are out of date" to every result when the skills
+    // this machine installed (recorded under $XDG_DATA_HOME/incur) predate the
+    // command surface. An empty data home keeps the suite's expected output
+    // independent of what the developer last ran `smthrs skills add` against.
+    env: {
+      NODE_COMPILE_CACHE: join(tmpdir(), "smithers-cli-test-compile-cache"),
+      XDG_DATA_HOME: join(tmpdir(), "smithers-cli-test-data-home")
+    },
     coverage: {
       enabled: true,
       provider: "v8",
