Workflows
A workflow defines the steps a task follows and what must happen before it moves on. Agents do the work; Enso runs any required checks and records whether it accepted the handoff. You can use one step or a development process with planning, implementation, review, and integration.
Checks are optional to configure and enforced once configured. A single
work → done flow with no tests, Git repository, or review is fully supported.
The development preset is a starting point you choose, not a requirement for every task.
Availability
This guide describes the transactional workflow implementation in Enso’s beta source.
It is not included in the published v0.1.2 release. Check enso workflow --help
on your installation before using these commands; an older installation does not gain
them just by changing its configuration. See releases
for published availability and installation for release updates.
Ask Enso to build a workflow
The bundled enso-workflow skill helps Enso inspect a project, choose its real validation
commands, configure stages and scripts, and test the result. For example:
Set up a development workflow for my app. Plan first, implement in a sibling worktree, require the project’s lint and unit tests, review the change, then integrate locally into main. Keep the jobs disabled until we have tried a small task.
Or start smaller:
Give this project one work step with no checks. I’ll add review later if I need it.
You can also edit the project configuration and job files directly. A project owns its ordered stages, checks, repository, and worktree settings. A stage job owns the agent instructions and, for agent work, the provider, model, and effort. Human stages have no job; command and integration stages use jobs without invoking a model.
How a handoff is accepted
Consider an implementation stage that requires lint and unit tests:
- Enso claims the task and reserves its worktree, if needed.
- The agent implements the change, commits it in the worktree, and submits a handoff. The task stays in implementation.
- After the agent and job postrun work stop, Enso runs lint and tests itself against the submitted candidate — the commits on the task’s branch. An uncommitted or untracked file in the worktree fails the candidate instead of being checked.
- If a check fails, Enso returns the actual output to the agent for repair within the configured limits. The repaired candidate must be checked again.
- When the required checks pass, Enso records acceptance, advances the task, and queues any configured lifecycle scripts. The next writer waits for ownership to be released.
That accepted handoff is the transaction: recorded progress backed by evidence. A failed attempt keeps its files for repair; it does not roll back shell commands or external effects. A provider’s successful exit or “tests passed” message cannot satisfy an Enso check. A stage with no checks simply requires a successful submitted handoff after execution finishes.
Missing results, timeouts, interrupted commands, and nonzero exits cannot pass a required check. Repair and review-return budgets persist across runs and restarts. Exhaustion or an unrecoverable failure leaves the task blocked with its work and diagnostics retained. Returning a broken candidate for changes, or blocking a task, does not require its forward checks to pass.
Start with one step
For an existing research workspace, create a project and its unchecked job:
enso project add RS --name Research --workspace research --flow basic
enso workflow init RS --preset basic
enso job show rs-work
This creates work → done and a disabled rs-work job. Edit that job’s instructions to
say what a useful result looks like, such as a short research note with source links.
There is no repository or mandatory evaluation. See stage jobs
for testing and enabling it.
Default development workflow
The development preset supplies:
Plan → Implement → Review → Integrate → Done
| Stage | What happens |
|---|---|
| Plan | An agent records scope, acceptance criteria, and a validation approach. It uses the workspace without allocating a worktree. |
| Implement | An agent changes code, useful tests, and related docs in the task worktree, then commits and submits. Enso runs the required lint and test commands, with up to two repairs. |
| Review | A separate stage run reviews the candidate against the task. It can return work to implementation up to twice. Review judgment is recorded separately from executable checks. |
| Integrate | Enso serializes local landing, rebases against the recorded target, reruns the required checks, and lands the accepted candidate. No model runs in this stage. |
| Done | Enso records the outcome, delivers configured completion scripts, and removes the worktree when cleanup is eligible. |
For an existing dev workspace and a repository whose real commands are npm ci,
npm run lint, and npm test:
enso project add APP --name "My app" --workspace dev \
--repo ~/Projects/my-app --flow basic --setup 'npm ci'
enso workflow init APP --preset dev --base main \
--worktree-root '../.worktrees/my-app' \
--lint 'npm run lint' --test 'npm test'
enso config check
enso job show app-implement
Use your repository’s actual commands and target branch. The preset requires both lint and test commands; an empty or missing command is refused. Add type checking, builds, or browser tests when they help establish the task’s acceptance criteria.
The preset creates four disabled jobs: app-plan, app-implement, app-review, and
app-integrate. Agent jobs snapshot the workspace’s agent choice, or the installation
default, when generated; you can select a different reviewer. Inspect every job, its
permissions, setup, and checks before enabling it. After a small manual trial, set
enabled: true in the jobs’ JOB.md files to allow ready work to run automatically.
For a manual trial, add a small task and run each stage only after the previous handoff has been accepted:
enso task add "Fix the empty-state label and add a regression test" --project APP
enso job run app-plan
enso workflow show APP-001
Use the reference returned by task add; it is not always APP-001. Continue with
app-implement, app-review, and app-integrate, inspecting evidence between runs.
A manual job run runs even when disabled and selects the next ready task in that stage;
use a scratch project or an otherwise empty queue for a controlled first trial.
Here, done means checked and integrated into the local target branch. The preset
does not push, open a pull request, or deploy. A workflow that ends at a human checkpoint
or a script that opens a pull request can define a different finish line.
Worktree location and concurrency
The product default is <repo>/.worktrees/<TASK-REF>, outside the Enso home. Set
worktree_root to a repository-relative path, an absolute path, or a path beginning with
~. The example above chooses a sibling root. This is useful when development servers
watch nested directories: Git ignoring a worktree does not necessarily stop a watcher
from rebuilding the main app.
Enso creates a task’s worktree only when a stage needs one, then reuses it across implementation, review, repair, and human checkpoints. It records the path, branch, starting revision, and target. Configuration changes affect new worktrees; existing ones keep their recorded identity. Setup installs dependencies in each worktree. Selected ignored-file copying is available without automatically sharing mutable dependency directories.
The dev preset sets max_concurrency to three. Different tasks can be planning,
implementing, and reviewing at once; each task worktree has one execution owner, and
integration is serialized per repository. A stage job itself has one active run.
Use a shared job concurrency_group for fixed ports, test databases, or other shared
resources. Worktrees do not isolate those resources. Checks that start servers must
clean them up, including on timeout and cancellation.
Blocked work and human-review worktrees are retained. Cleanup waits for active execution and worktree-using hooks; dirty or unmerged work is preserved. Ignored files can be removed with an otherwise clean worktree: copy durable artifacts elsewhere or archive them during teardown. See integration and cleanup.
Other examples
These are project configuration snippets to merge into config.json, not complete home
configurations. Create the named workspaces first. Agent, command, and integration stages
each need a matching stage job; changing the stage list alone does
not create jobs. Each example script is project-authored: provide it and test its exit behavior
before making it a required check.
A writing flow with human approval
research → draft → approve → done keeps the final decision with a person, without
requiring Git or executable checks:
{
"projects": {
"WR": {
"name": "Writing",
"workspace": "writing",
"stages": ["research", "draft", "approve:human"]
}
}
}
Create jobs for research and draft. The approval stage waits for an operator; advancing it means the draft is approved, not published. You can add an executable citation or formatting check to the draft stage later without changing how the agent writes.
A checked implementation followed by a person
For small changes, skip planning and automated integration. Run tests after implementation
and stop at approve for a person to decide how to land the change:
{
"projects": {
"FIX": {
"name": "Small fixes",
"workspace": "dev",
"repo": "~/Projects/my-app",
"base": "main",
"worktree_root": "../.worktrees/my-app-fixes",
"setup": "npm ci",
"stages": [
{
"name": "implement",
"checks": [
{"name": "lint", "command": "npm run lint", "timeout": 600},
{"name": "unit", "command": "npm test", "timeout": 600}
],
"max_repairs": 1
},
{"name": "approve", "human": true}
]
}
}
}
The agent gets one repair opportunity. At approve a person decides: advance the task, or
send it back with enso task return for another pass through implementation. max_repairs
and max_returns bound the loops an agent run can take, so a human stage does not need
them. This workflow contains no integration stage: approval alone does not merge anything,
and unmerged work remains available after completion.
An automated validation step without a model
prepare → validate → approve → done can assemble a report with an agent, then validate
its artifact with an ordinary program before a person reviews it:
{
"projects": {
"RP": {
"name": "Reports",
"workspace": "reports",
"stages": [
"prepare",
{"name": "validate", "command": "python3 scripts/validate_report.py"},
"approve:human"
]
}
}
}
The validation command runs in the reports workspace. Have it use ENSO_TASK to find the
right task’s artifact, print useful diagnostics, and exit nonzero when validation fails.
It can check required files, schema, references, or a numerical threshold. No model is
invoked for this stage. An external CI check can use the same pattern, provided its script
waits for the relevant candidate’s completed result and fails on missing or skipped checks.
Checks and lifecycle scripts
Use checks when failure must prevent progress, and lifecycle hooks for reactions to an already-recorded transition:
| Need | Put it in |
|---|---|
| Install per-worktree dependencies | Project setup |
| Require lint, tests, a build, or a benchmark threshold | The stage’s checks array |
| Run a whole step without a model | The stage’s command |
| Require a person’s decision | A human stage |
| Update a local index when a task finishes | Project hooks["after:done"] |
| Archive artifacts before removing a worktree | Project hooks.teardown |
For example, add these fields to a project whose scripts already exist:
{
"hooks": {
"after:done": "bash scripts/record-completion.sh",
"after:blocked": "bash scripts/record-blocker.sh",
"teardown": "bash scripts/archive-task-artifacts.sh"
},
"script_timeout": 120
}
Hooks receive task, project, stage, repository/worktree, and attempt context, including a
stable ENSO_EVENT_ID. They run in the recorded worktree when available, otherwise the
workspace. Delivery can repeat, so scripts must deduplicate that event ID or be safe to
repeat. A failed after-transition hook stays visible and is retried; it does not undo the
accepted stage. Teardown failure retains the worktree. Never move the task recursively
from a lifecycle hook. Task lifecycle details and the
script environment define the full contract.
Ordinary job prerun/postrun hooks remain useful, but putting a test in postrun is not the same as configuring a required task check. Put acceptance requirements in the stage so Enso can enforce them through all supported handoff paths.
Inspect and recover
Open a task in the web viewer to see submissions, checks, repair attempts, review handoffs, integration, and cleanup. The workflow history distinguishes a provider’s outcome from stage acceptance. Command output and candidate/spec/workflow versions remain available for inspection; old failed attempts are not rewritten as passes.
enso task show APP-001
enso workflow show APP-001 --json
Fix the cause before retrying. An operator can use workflow retry with a reason to reset
an exhausted budget, then task resume for blocked work; retry does not resume the task
or manufacture passing checks. workflow verify submits an operator handoff, runs the
configured checks without a provider, and advances the task when accepted. On an
integration stage it can also land the candidate locally. The viewer labels it
Operator verification. Use the stage job for a command stage: manual verification
does not execute that stage’s command. See CLI recovery commands
for the full contract.
Checks are tied to the candidate and chosen specification/workflow. A changed candidate,
rebase, or changed acceptance inputs requires fresh evidence. Existing tests and common
validation manifests are protected; intentional changes require explicit operator review
with workflow approve-rules, followed by real verification. Agents cannot use these
operator commands through the supported in-run CLI, and --force does not bypass checks.
These controls enforce Enso’s supported workflow paths. They are not an OS sandbox against an unrestricted agent running as the same account. Provider permissions and separately protected execution are needed for that stronger boundary.
Migrate an existing flow
Inspect the existing tasks, jobs, and scripts; disable the project’s stage jobs and let
active work finish, because migration refuses to start while a project job is running, a
task is claimed, or a lifecycle script is still pending.
Then use the development initialization command with --migrate rather than overwriting
an existing pipeline. Migration maps triage to plan and todo to implement, preserves
task history and recorded worktrees, and retains old job definitions and scripts.
Review custom stage mappings and carry useful instructions into the new jobs.
Move acceptance tests into stage checks and completion reactions into lifecycle hooks. The replacement jobs start disabled. Validate them, exercise a failed check and repair, and inspect the task history before enabling queues. See the migration procedure for backups and interrupted migration recovery.
