Pipeline State
pipeline-state/
A small file-backed handoff bus for multi-agent runs: each phase records its outputs and evidence paths, so later phases look them up instead of re-pasting them. Survives context compression.

A durable, file-backed handoff bus so multi-agent runs pass references, not giant inlined dumps.
Why use it
- Phase 4 re-pastes everything phase 3 produced, and the context window pays for it twice.
- A long run gets compressed and the earlier phases' outputs are simply gone.
- "What did the last phase actually produce?" has no machine-readable answer.
What it does
- One per-run, git-ignored JSON file holding the run's variables, each phase's outputs, and paths to its evidence.
- Read-on-START / write-on-COMPLETION protocol: pull your inputs by lookup, record your outputs as fields + PATHS.
- A small shell script,
init/set/append/get/clear, plus the state schema. - Coexists with your committed reports: it references them, it never replaces them.
Benefits
- Small prompts, long runs. Handoffs become lookups instead of re-pasted dumps.
- Survives context compression: nothing from phase 2 is lost by phase 8.
- Gates you can trust. The orchestrator validates from evidence, not from a summary.
- One script, any pipeline. No service, no database, no dependencies.
Install
Download this skill and copy the pipeline-state folder to .claude/skills/pipeline-state/ in your project, or to ~/.claude/skills/pipeline-state/ to use it in every project. Claude Code finds it on its own; you can also call it with /pipeline-state.
Part of the Context Engineering bundle. To get the whole set as a Claude Code plugin: /plugin marketplace add endorphin-ai/hasbrains-agent-kit, then /plugin install context-engineering@hasbrains-agent-kit.
Source: endorphin-ai/hasbrains-agent-kit. License: MIT.
Files (7)
Files
#!/usr/bin/env bash
# pipeline-state.sh — durable run-wide handoff-bus state (the orchestrator + every worker agent)
# Usage: pipeline-state.sh <init|set|append|get|clear> <session_id> [field] [value]
#
# State file (PER-SESSION, git-ignored): .ai_log/session-<session_id>.json (one per run; the
# orchestrator `init`s it at session start and passes its PATH as state_file to every agent). It
# lives under .ai_log/, so it is git-ignored (never committed) by the /.ai_log/* rule — the ephemeral
# machine handoff bus, NOT a durable record (durable records live in committed docs/).
# <session_id> is the run LABEL (convention: <YYYY-MM-DD>-<epic-slug> = <id>-<name>); the file PATH is
# DERIVED from it (session-<session_id>.json), so every command resolves the same per-session file.
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
AI_LOG="$REPO_ROOT/.ai_log"
CMD="${1:-}"
SESSION="${2:-}"
FIELD="${3:-}"
VALUE="${4:-}"
if [ -z "$CMD" ] || [ -z "$SESSION" ]; then
echo "Usage: $0 <init|set|append|get|clear> <session_id> [field] [value]" >&2
exit 1
fi
# Per-session, git-ignored state file under .ai_log/ — DERIVED from the session label.
# The orchestrator `init`s it and passes this exact PATH as state_file to every agent.
FILE="$AI_LOG/session-${SESSION}.json"
case "$CMD" in
init)
mkdir -p "$AI_LOG"
# Clean stale .ai_log/ artifacts from prior runs (R15: .ai_log/ is temporary-only).
# Preserve the tracked .gitkeep so the folder stays committed. Runs BEFORE writing $FILE,
# so the fresh per-session state file is written into a clean .ai_log/.
find "$AI_LOG" -mindepth 1 ! -name .gitkeep -exec rm -rf {} + 2>/dev/null || true
jq -n --arg s "$SESSION" '{session: $s, phases: {}}' > "$FILE"
echo "pipeline-state: initialized $FILE (session=$SESSION)"
;;
set)
[ -z "$FIELD" ] && { echo "Error: field required" >&2; exit 1; }
[ -f "$FILE" ] || { mkdir -p "$AI_LOG"; echo '{}' > "$FILE"; }
tmp=$(mktemp)
jq --arg k "$FIELD" --arg v "$VALUE" '.[$k] = $v' "$FILE" > "$tmp" && mv "$tmp" "$FILE"
;;
append)
[ -z "$FIELD" ] && { echo "Error: field required" >&2; exit 1; }
[ -f "$FILE" ] || { mkdir -p "$AI_LOG"; echo '{}' > "$FILE"; }
tmp=$(mktemp)
jq --arg k "$FIELD" --arg v "$VALUE" '.[$k] = ((.[$k] // []) + [$v])' "$FILE" > "$tmp" && mv "$tmp" "$FILE"
;;
get)
[ ! -f "$FILE" ] && { echo "Error: no state file at $FILE (run 'init' first)" >&2; exit 1; }
if [ -z "$FIELD" ]; then
jq . "$FILE"
else
jq -r --arg k "$FIELD" '.[$k] // empty' "$FILE"
fi
;;
clear)
rm -f "$FILE"
echo "pipeline-state: cleared $FILE"
;;
*)
echo "Unknown command: $CMD" >&2
echo "Usage: $0 <init|set|append|get|clear> <session_id> [field] [value]" >&2
exit 1
;;
esac