Communicating Between Steps

Cloche offers three mechanisms for passing data between steps: environment variables injected by the daemon, a KV store accessible from both host and container steps, and a shared run folder for large files. Which one to use depends on what you’re passing and where it needs to go.

Environment Variables

The daemon injects a set of CLOCHE_* environment variables into every step. These provide context about the current run — IDs, paths, and output locations — rather than user data.

Host steps

Variable Description
CLOCHE_PROJECT_DIR Absolute path to the project directory on the host.
CLOCHE_RUN_ID Run ID for this workflow execution (e.g. a133:develop).
CLOCHE_PREV_OUTPUT Path to the output file from the immediately preceding step.
CLOCHE_TASK_ID Task ID assigned by the daemon (set for the main phase).
CLOCHE_ATTEMPT_ID Attempt identifier for this run.

Container steps

Variable Description
CLOCHE_RUN_ID Run ID for this workflow execution (e.g. a133:develop).
CLOCHE_TASK_ID Task ID assigned by the daemon. Set when the run is associated with a task.
CLOCHE_ATTEMPT_ID Attempt identifier for this container run.
CLOCHE_PROJECT_DIR Working directory inside the container (/workspace).
CLOCHE_AGENT_COMMAND Overrides the default agent command inside the container.
CLOCHE_ADDR Daemon gRPC address (e.g. host.docker.internal:50051). Used by clo.
ANTHROPIC_API_KEY Passed through from the host environment if set.

KV Store

The KV store is the primary way to pass user data between steps. It is backed by the daemon’s gRPC API and is accessible from both host scripts and container steps, making it the natural choice for cross-boundary communication.

  • Host scripts use cloche get <key> and cloche set <key> <value>
  • Container steps use clo get <key> and clo set <key> <value>

Both commands require CLOCHE_TASK_ID (and CLOCHE_ATTEMPT_ID if set), which the daemon injects automatically.

# In a host script step:
cloche set pr_id 1234

# In a later container step:
pr_id=$(clo get pr_id)

Values are scoped to the run and persist for its lifetime. Any step in the run can read keys written by any earlier step.

Reading from stdin or files

cloche set and clo set accept - to read from stdin, or -f <file> to read from a file:

cloche set description -         # read from stdin (trailing newlines trimmed)
cloche set config -f config.json # read from file

Auto-seeded keys

The daemon writes several keys automatically before each step runs:

Run-level (set once at run start):

Key Value
task_id Task identifier (same as CLOCHE_TASK_ID)
attempt_id Attempt identifier
workflow Current workflow name (host runs)
run_id Run identifier
temp_file_dir Scratch directory at .cloche/runs/<run-id> for files too large for the 1 KB KV limit

Step-level (updated as steps complete):

Key Value
prev_step Name of the most recently completed step (empty for the entry step)
prev_step_exit Exit code of the most recently completed step (empty for the entry step)

Step result tracking (set after each step completes):

Key Value
<workflow>:<step>:result Result of the completed step (e.g. develop:implement:result = success)

Sub-workflow results (published after a workflow_name step targeting a container workflow completes, under the host run’s scope so host steps can read them):

Key Value
child_run_id Run ID of the container sub-workflow
child_branch cloche/<attempt>-<container> — branch the daemon pre-created and extracted the container’s results onto. Its worktree lives at .gitworktrees/cloche/<branch-suffix>
child_repos Comma-separated repo names the sub-workflow declared (multi-repo runs)
child_branch:<repo> Per-repo result branch (multi-repo runs)
child_repo_path:<repo> Per-repo host path (multi-repo runs)

Convention keys set by the generated scaffold scripts (not by the daemon):

Key Set by Value
task_prompt_path prepare-prompt.py Path to the task description file under temp_file_dir; read in-container via clo get
worktree_path merge.py The daemon-created worktree being merged; feeds the fix-merge prompt as {{ $worktree_path }}
base_branch merge.py The branch being merged into; feeds the fix-merge prompt as {{ $base_branch }}

All auto-seeded keys are writable — scripts can overwrite them with cloche set if needed.

Listing keys

Inside a container, clo keys lists all keys in the current run namespace.

Shared Run Folder

KV values are limited to 1 KB. For larger data — diffs, logs, generated files — write to the shared run folder and pass the filename through the KV store.

The run folder is at .cloche/runs/{CLOCHE_RUN_ID}/. It is a bind mount shared between the host and the run’s containers, so files written on either side are visible to the other. The daemon creates this directory at run start.

# Host script — write a large review payload
output_dir=$(cloche get temp_file_dir)
generate-feedback > "$output_dir/review-feedback.md"
cloche set review_feedback_path "$output_dir/review-feedback.md"

# Container step — read it back
feedback_path=$(clo get review_feedback_path)
cat "$feedback_path"

The built-in KV key temp_file_dir is set automatically by the daemon and points to .cloche/runs/<run-id>. Use it rather than constructing the path yourself.

Run folder cleanup

When a workflow run exits, the daemon deletes its run folder automatically. Do not store anything in .cloche/runs/ that needs to outlive the run. If a step produces artifacts that should persist (e.g. a built binary or a report), copy them to a location outside the run folder before the workflow completes.

Example: Passing Task Data Through a Pipeline

A common pattern is for a host script step to extract data from a task, write it to the shared run folder, and pass the filename via the KV store for downstream steps to consume:

# .cloche/scripts/prepare-prompt.sh (host script step)

# Read the task description from the tracker
task_id="$CLOCHE_TASK_ID"
description=$(gh issue view "$task_id" --json body -q .body)

# Write it to the shared run folder (handles files > 1 KB)
output_dir=$(cloche get temp_file_dir)
prompt_file="$output_dir/task_prompt.md"
echo "$description" > "$prompt_file"

# Store the path so downstream steps can find it
cloche set task_prompt_path "$prompt_file"

A container step later in the pipeline reads it back:

# Inside a container step (or in a prompt template via clo)
prompt_path=$(clo get task_prompt_path)
cat "$prompt_path"

This keeps large payloads out of the KV store (which is limited to 1 KB values) while still using it as the coordination mechanism. The scaffolded prepare-prompt.py script follows exactly this pattern, setting task_prompt_path for the container workflow to read.

Which mechanism to use

Need Mechanism
Run context (IDs, paths) Environment variables — already injected
Small values between steps (< 1 KB) KV store (cloche set / clo get)
Large files between steps (> 1 KB) Shared run folder + KV store for the path

Asking the User

Steps can also communicate with you, not just each other. Inside a container, clo ask "question" (or the ask_user MCP tool from an agent) opens a help thread and blocks until you reply with cloche threads reply or via a configured integration such as Slack. If no reply arrives within park_after (default 5m), the run is parked — container committed and stopped — and resumes automatically when a reply lands. See cloche threads and the [help] config section.