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>andcloche set <key> <value> - Container steps use
clo get <key>andclo 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.
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.