Resume and state
Unstable task IDs break resume
The runtime keys completed work by taskid. A changed id looks like a brand new task, and an id that disappears is dropped from the plan. Derive ids from data, never from a loop index or a timestamp.
Input is immutable after the first run
A run’s--input is persisted when it starts. Resuming with different input is an error, not a silent override. If you need different input, start a new run.
Code changes block resume, they do not merge
A workflow source change is a different workflow. Resume validates the source hash of the original run, so editing the file and then resuming is blocked. Start a new run instead. To change a workflow that is still running, use hot reload (up --hot): edits apply to newly scheduled tasks while in-flight tasks finish on their original code. See Recipes.
useState is not durable
React state resets on every render, which here means every frame. Anything that must survive a crash belongs in a Task output read back through ctx, not in component state.
Caching
Do not cache side-effecting tasks
cache is for pure work that is expensive to recompute. Caching a deploy, an email, or a mutation means it silently does not run on a cache hit. The cache key is cache.by(ctx) plus cache.version plus the output schema signature, so a schema change invalidates the cache automatically and a stale cached row fails validation and misses safely. See How It Works.
Side effects and retries
Mark side-effecting tools and key them
Tasks retry, and a retried agent loop can call a tool again. A custom tool that writes to the world should declaresideEffect: true and pass ctx.idempotencyKey through to the downstream system so a retry is a no-op rather than a second charge. ctx.idempotencyKey is stable across retries and resumes for the same task iteration.
Tools and sandbox
Agents get only the tools you grant
The five built-in tools (read, write, edit, grep, bash) are sandboxed to rootDir. Symlinks, network, and long-running calls are denied by default; --allow-network opens bash to the network. Grant least privilege per task: a reviewer gets read and grep, an implementer gets write, edit, and bash, and an agent with no tools cannot touch the filesystem at all. See How It Works.
Time travel and VCS
Revert and VCS-restoring replay change your working tree
These rewrite filesystem state, so treat them the way you would treatgit checkout over uncommitted work.
revertrestores the workspace to a previous attempt’s filesystem state and discards graph snapshots recorded after that attempt. It restores files only and lands them as a new change on top of the current working copy. See Revert to Attempt.replay --restore-vcschecks out the jj revision the snapshot was taken at, so re-execution sees the same source as the original run.
revert requires jj
Smithers prefers .jj over .git. Pure Git repos run fine but cannot use revert, because there is no per-attempt change to restore. Install jj if you want attempt-level revert. See VCS.
Worktree runs auto-rebase on resume
On resume of a worktree run, Smithers rebases onto the base branch (defaultmain) and continues even if the rebase fails. Expect the branch to move.
Outputs
ctx.outputMaybe is undefined until the task runs
Reading a downstream output before its task has completed returns undefined, not a default. Guard it so a not-yet-run task does not crash the render.
Read next
- How It Works: the execution model these rules come from.
- Recipes: caching, hot reload, and VCS revert in context.