---
title: Hot Reload
description: Edit workflow code while a run is executing -- prompts, config, agents, and component structure update live without restarting.
---

Your workflow has been running for forty minutes. Three tasks are done. The fourth is in progress. You just realized the prompt for the fifth task is wrong.

Without hot reload, you kill the process, fix the prompt, and start over. Forty minutes gone.

With hot reload, you save the file. The engine picks up the change. The in-flight task finishes with its original prompt. The fifth task uses your new prompt. Zero wasted work.

## Quick Start

Add `--hot true` to any `up` command:

```bash
smithers up workflow.tsx --hot true
smithers up workflow.tsx --run-id abc123 --resume true --hot true
```

That is it. Edit any file in your workflow's directory tree, save, and the engine picks up the changes on the next render cycle.

## How It Works

Smithers is built on React. Your workflow's `build(ctx)` function is a React component tree that the engine re-renders every loop iteration using a [custom React reconciler](/concepts/planner-internals). All run state lives in SQLite, not in the React fiber tree.

Hot reload leverages that architecture in five steps:

1. **Watch** -- The engine watches your workflow's directory tree (excluding `node_modules/`, `.git/`, `.jj/`, `.smithers/`).
2. **Overlay** -- On file change, Smithers creates a "generation overlay" -- a copy of your source tree with fresh file URLs so every module (including transitive dependencies) is re-evaluated.
3. **Import** -- The new workflow module is imported from the overlay. `createSmithers()` returns the cached DB connection and schema maps (no duplicate connections).
4. **Swap** -- Only `workflow.build` is replaced. The database, schema registry, and all persisted state remain untouched.
5. **Wake** -- The engine loop is woken immediately (even if tasks are still running) so it re-renders with the new code.

```
File saved -> watcher detects -> overlay built -> module imported -> build swapped -> re-render
                                                                                       |
                                                                         new tasks use new code
                                                                         in-flight tasks unaffected
```

The key insight: state is in SQLite, not in your code. Swapping the code does not lose state.

## What You Can Change Live

These changes take effect on the next render cycle:

| Change | Effect |
|---|---|
| Prompt strings / `.md` files | New tasks get the updated prompt |
| Focus lists, config values | Scheduler sees new priorities |
| Agent configuration (model, timeout, system prompt) | New agent instances for new tasks |
| JSX tree structure (add/remove/reorder tasks) | New plan tree on next render |
| Concurrency / retry settings | Applied to newly scheduled tasks |

## What Requires a Restart

These changes are blocked to prevent data corruption:

| Change | Why |
|---|---|
| Output Zod schemas (shape changes) | Schema identity is used for output table resolution |
| `createSmithers()` dbPath | Would create a second database connection |
| Adding/removing output schema keys | Changes the schema registry |

If you attempt a schema change, the engine logs a warning and keeps running with the previous code:

```
[00:12:34] Warning: Workflow reload blocked: Schema change detected; restart required to apply schema changes.
```

Why the hard line on schemas? Because output tables are keyed by schema identity. If you change a schema mid-run, existing rows would no longer match the new shape. That is data corruption. So Smithers refuses and tells you to restart.

## In-Flight Task Behavior

When a hot reload changes the task graph, tasks that are already running are **not cancelled**. They continue with the code they were launched with and their output is persisted normally.

This means:

- A task started with prompt v1 will finish with v1, even if you have since saved v2.
- If a reload removes a task from the tree, its in-flight attempt still completes. The output may go unused by downstream tasks.
- If a reload changes a task's `id`, the old in-flight attempt is treated as a different node. Both may run.

That last point is worth repeating. Changing a task ID during a hot reload does not "rename" the task. It creates a new one. The old one is orphaned.

## CLI Output

When hot reload detects and applies changes, you will see:

```
[00:05:12] File change detected: 1 file(s)
[00:05:12] Workflow reloaded (generation 1)
```

On errors:

```
[00:05:12] Warning: Workflow reload failed: SyntaxError: Unexpected token
```

The workflow continues running with the last valid code. Fix the error and save again. No panic required.

## Events

Hot reload emits events through the standard [event system](/runtime/events):

| Event | When |
|---|---|
| `WorkflowReloadDetected` | File changes detected (before reload attempt) |
| `WorkflowReloaded` | Reload succeeded; includes `generation` number and `changedFiles` |
| `WorkflowReloadFailed` | Reload failed (syntax error, import error); includes `error` |
| `WorkflowReloadUnsafe` | Reload blocked (schema change); includes `reason` |

These events are persisted to the NDJSON event log and visible via `onProgress`.

## Options

| CLI Flag | RunOptions field | Description | Default |
|---|---|---|---|
| `--hot` | `hot: true` | Enable hot reload | Disabled |

Advanced options (via `RunOptions.hot` object):

| Field | Description | Default |
|---|---|---|
| `rootDir` | Directory to watch | Workflow file's directory |
| `outDir` | Overlay output directory | `.smithers/hmr` |
| `maxGenerations` | Number of overlay generations to keep | `3` |
| `cancelUnmounted` | Cancel in-flight tasks that become unmounted after reload | `false` |
| `debounceMs` | Debounce interval for file change events | `100` |

## Tips

### Keep prompts in separate files

If your prompts live in `.md` or `.ts` files imported by your workflow, editing them triggers a hot reload automatically. This is the most common workflow: edit a prompt, save, watch the next task pick it up.

```ts
// prompts/planning.md changes -> hot reload -> new tasks use updated prompt
import planningPrompt from "./prompts/planning.md";

export default smithers((ctx) => (
  <Workflow name="my-workflow">
    <Task id="plan" agent={new ClaudeCodeAgent({ systemPrompt: planningPrompt })}>
      ...
    </Task>
  </Workflow>
));
```

### Avoid module-scope side effects

Code that runs at import time (outside of `build()`) is re-executed on every reload. If your module-level code opens a file, starts a server, or prints a banner, that will happen again on every save. Keep side effects inside `build()` or use `createSmithers()` which is automatically cached in hot mode.

### Use with resumability

Hot reload and [resumability](/guides/resumability) work together naturally. You can:

1. Start a run with `--hot`
2. Edit files while it runs
3. Kill the process (Ctrl+C)
4. Resume with `smithers up workflow.tsx --run-id <id> --resume true --hot true`

The resumed run picks up your latest code and continues watching for changes.

## Related

- [Execution Model](/concepts/execution-model) -- How the render-schedule-execute loop works.
- [Resumability](/guides/resumability) -- How crash recovery preserves state.
- [Events](/runtime/events) -- Subscribing to lifecycle events.
- [CLI Reference](/cli/overview) -- Full CLI flag reference.
