Basic Concepts

Cloche interprets ‘.cloche’ files containing a DSL (domain specific language) for defining workflows. The syntax is intended to be simple and flexible, acting as the glue binding the steps in your workflow together.

Environment

Cloche Workflows run either on the Host or within a Docker Container. The run environment is bound to the entire Workflow and is the primary mechanism by which we can isolate riskier actions (such as yolo-mode LLM development) to a containerized environment, while also keeping access to the host machine where necessary for things like merging changes back to the main repository.

The environment is specified by including a mandatory host{} or container{} configuration block. The blocks may be left empty if not overriding the default setting values, but one of them must be present in the workflow definition:

workflow "host-side" {
  host {}
}

workflow "container-side" {
  container {}
}

Containerized Workspace

Containerized environments copy your project into /workspace/ within the container. This is in lieu of a container mount to avoid parallel tasks from conflicting while modifying files - and to keep your host file system safe should the LLM erroneously decide to run rm -rf / --no-really-i-mean-it.

You don’t need to put this step in your Dockerfile - Cloche will automatically copy your project into the container when it starts up. (This also keeps the project code up-to-date with the Host without rebuilding the container every time.)

Container Isolation

Your project directory is never modified by the container. Around every container sub-workflow the daemon pre-creates a result branch (cloche/<attempt>-<container>) with its own worktree under .gitworktrees/cloche/, and when the sub-workflow completes it extracts the container’s /workspace onto that worktree and commits it there — so you always review the agent’s changes before merging. Auth files are copied — not bind-mounted — so each container gets its own isolated copy, and only selectively: .credentials.json, settings.json, and settings.local.json from ~/.claude/, plus ~/.claude.json for interactive containers only. Override files from .cloche/overrides/ are applied on top of /workspace/ at container start.

For a deeper look at the security model, network controls, and credential handling, see the Safety Guide.

Workflows

Workflows are the primary concept in Cloche. Every behavior Cloche triggers for you must be defined as part of a Workflow.

Workflow files must be inside the <project>/.cloche/ directory. They may have any file name, but the extension must be .cloche e.g. main.cloche or review.cloche. You can define workflows in any *.cloche file, putting all your definitions in a single file or splitting them up with a single workflow defined per file.

Workflow files define a collection of

  • Steps: The individual units of work
  • Wires: The connections between Steps
  • Configuration: Settings which control the workflow environment
workflow "develop" {
  // Comments begin with double-slash

  // This workflow runs on the container and specifies the name of the image to use.
  container {
    image = "my-project:latest"  // The default is whatever is in .cloche/Dockerfile, but you can override for different workflows
  }

  // The first step defined is the entry point to the workflow. After this, order doesn't matter.
  step implement {
    prompt  = file(".cloche/prompts/implement.md")  // Prompts can be inline strings, or a file reference.
    results = [success, fail]  // By default, an exit code of 0 means success and all others are fail.
  }

  step test {
    run     = "make test"   // 'run' defines a 'script' step which executes the command or file provided.
    results = [success, fail]
  }

  // Wires define the connections between Steps
  implement:success -> test   // Here, the 'success' wire connects implement to test.
  implement:fail    -> abort

  test:success      -> done   // 'done' and 'abort' are special "terminal" states. They don't exist as Steps. They're how we signal to Cloche that the workflow is finished.
  test:fail         -> abort
}

Steps

A step is a single executable unit of work. Cloche infers the type from which field it declares:

  • prompt: Executes a Prompt using the configured LLM Agent
  • run: Executes a command in the workflow’s Environment
  • workflow_name: Starts another Workflow
  • poll: Pauses the workflow until the poll script triggers the next wire

The step {} block declares its type by specifying one of these fields and the string or file needed.

Prompt Steps

A prompt step invokes an LLM agent with a task description. The agent runs autonomously inside the container (or on the host, for host workflows) and reports its outcome by printing a marker to stdout. Agent markers are framed with a per-step random nonce — CLOCHE_RESULT:{{ $result_nonce }}:<name> — so that incidental mentions of CLOCHE_RESULT:success in the agent’s transcript (grepping code, editing test fixtures) are not mistaken for a result. Cloche appends the exact marker to print to the assembled prompt, so you don’t normally write it yourself. A marker is required regardless of exit code: an agent that exits 0 without printing one gets a single recovery turn asking for just the marker, and is classified fail if it still emits none. See Result Protocol.

step design {
  prompt  = file(".cloche/prompts/design.md")
  results = [success, fail]
}

The prompt field can be an inline string or a file(...) reference. By convention, prompt files live in .cloche/prompts/ and are written in Markdown.

Script Steps

A script step runs a shell command. Exit code 0 maps to success, any non-zero exit maps to fail, unless the script explicitly prints CLOCHE_RESULT:<name> to override.

step test {
  run     = "make test 2>&1"
  results = [success, fail]
}

Script steps are the right choice for deterministic checks: running tests, linters, validators, build commands, or any shell pipeline with a clear pass/fail outcome.

Poll Steps

A poll step pauses a workflow until an external input is available. Cloche runs the script at the specified fixed interval and suspends the workflow until the script reports a decision. This ensures that e.g. containers and their run state are preserved to support iterative workflow processes. This is the right primitive for awaiting human-facing approval gates, code-review, CI result checks, or anywhere the pipeline needs to pause for an external source.

Important: Poll steps have more manual Wiring requirements than other Step types. Unlike other Step types, we need to support a pending case - no results are available yet, so we need to stay in this workflow step.

The polling script reports exit wire decisions the same way as any other Step — by printing CLOCHE_RESULT:<wire name> to stdout. The key difference is what happens when no marker is printed: exit 0 with no marker means pending and Cloche will poll again after interval. Non-zero exits with no marker follows the fail wire.

In order to select a non-pending, non-failure wire, the Poll step script must specify the exit wire by printing CLOCHE_RESULT:<wire name> to stdout. e.g. in the code-review example below, there are four wires defined: pending, fail, approved, fix. The first two will be triggered as defaults based on exit code alone. The latter must be printed to STDOUT (regardless of exit code - the explicit CLOCHE_RESULT: text takes precedence) once the review feedback is received. Poll scripts are author-controlled, so they use the bare marker — the nonce that frames prompt-step markers does not apply.

In a host workflow, a run waiting at a poll step also gives up its concurrency slot so the orchestration loop can launch other work, and takes one back (ahead of new task launches) when the poll resolves.

step code-review {
  poll     = "bash .cloche/scripts/check-pr-review.sh"
  interval = "5m"
  timeout  = "48h"
  results  = [approved, fix] // 'fix' could forward to 'address-feedback' which in turn could return to this step, creating a human-in-the-loop review flow.
}

Wires

Wires are the named edges that connect steps. Each step declares its possible results, and a wire maps a specific step:result pair to the next step to run.

implement:success -> test
implement:fail    -> abort
test:success      -> done
test:fail         -> fix
fix:success       -> test
fix:fail          -> abort

done and abort are built-in terminal targets. Every workflow must have at least one wire reaching done - you should probably have something wired to abort as well, but if your workflow can only succeed, it’s fine.

You can specify multiple targets for the same wire to create parallel flows (e.g. run ‘code-review’, ’lint-check’ and ‘integration-test’ steps at once) by creating multiple lines:

test:success -> code-review
test:success -> lint-check
cancel-and-cleanup:success -> abort  // cleanup workflow for abort cases
cancel-and-cleanup:fail    -> abort

You can also define a Step must await multiple Steps completion using collect with all or any keywords:

collect all(code-review:success, lint-check:success) -> done  // wait for all to succeed before we're done
collect any(code-review:fail, lint-check:fail) -> cancel-and-cleanup  // trigger early termination

Configuration

Within a workflow are also some configuration options to tune your workflow’s Steps to your specifications

Agent Block

An agent is a named LLM harness: a command plus optional arguments that Cloche runs when executing prompt steps. Declaring agents lets prompt steps share the same configuration and makes it simple to use different LLMS for different purposes.

workflow "develop" {
  agent claude {
    command = "claude"
    args    = "-p --output-format stream-json"
  }

  agent codex {
    command = "codex"
    args    = "--full-auto"
  }

  step implement {
    prompt  = file(".cloche/prompts/implement.md")
    agent   = claude
    results = [success, fail]
  }

  step review {
    prompt  = file(".cloche/prompts/review.md")
    agent   = codex
    results = [success, fail]
  }

  implement:success -> review
  implement:fail    -> abort
  review:success    -> done
  review:fail       -> implement
}

If a prompt step does not reference an explicit agent, Cloche falls back to the workflow-level default (container { agent_command = ... } or host { agent_command = ... }), then to the CLOCHE_AGENT_COMMAND environment variable, and finally to claude. See Agent Setup for the full resolution order and for how to configure authentication for each supported agent.

Orchestration Model

Cloche’s orchestration loop (cloche loop) uses a two-phase model to discover and execute work automatically.

Phases

Phase Workflow name Purpose
1 list-tasks Discover available work. Final step output is parsed as JSONL (one task per line).
2 main Do the work. Receives a task ID via the CLOCHE_TASK_ID env var.

Only main is required. If list-tasks is absent, the daemon runs main continuously using a sentinel task (no task ID tracking).

The list-tasks workflow output is JSONL. Each line is a JSON object:

Field Required Description
id yes Unique task identifier
status yes One of open, closed, in-progress
title no Short summary
description no Full description
metadata no Arbitrary key-value pairs

The daemon picks the first task with status open and runs main with that task ID. Tasks are deduplicated within a configurable timeout window (see Configuration Reference) to prevent rapid reassignment.

Task claiming and cleanup

Cloche discovers tasks but does not manage external tracker state. Your workflow scripts are responsible for:

  • Claiming a task — the main workflow typically starts with a script step that marks the task as in-progress in your tracker (e.g. assigning a GitHub issue, moving a Jira ticket).
  • Cleanup on completion — a finalization step that closes the issue, pushes the branch, opens a PR, or whatever your pipeline requires.
  • Cleanup on failure — unclaiming the task so it returns to the open pool for a future attempt.

Releasing and closing tasks from the dashboard

Two optional host workflows back the actions in the web dashboard’s Needs you pane (see Web Console). Neither is part of the automatic loop — both run on demand and receive CLOCHE_TASK_ID for the task in question.

  • release-task — backs the Release claim action. Returns a stale claimed task (one that is in_progress with no active worker, e.g. after a failed run or daemon restart) to open status.
  • close-task (or cancel-task) — backs the Close in tracker action. Marks a task done or cancelled in your tracker without going through the normal main pipeline — e.g. giving up on a task that has failed too many times in a row. If a project defines neither name, the dashboard disables the action with a hint instead of failing at click time.

A typical close-task reuses the same script your main pipeline’s own close step calls on success:

workflow "close-task" {
  host {}

  step close-task {
    run     = "bash .cloche/scripts/close-task.sh"
    results = [success, fail]
  }

  close-task:success -> done
  close-task:fail    -> abort
}

Learn More

From here, the DSL Reference walks through the complete syntax, and the GitHub Issues tutorial shows how to compose these primitives into a full automation pipeline with a human-in-the-loop review cycle.