| name | automated-orchestration |
| description | Use when building multi-stage analysis pipelines that need checkpoint/resume, when long-running jobs fail mid-execution and lose progress, when processing large data archives that exceed memory or rate limits, or when analysis depth should adapt to data volume |
Automated Orchestration with Checkpoints
Overview
Coordinate end-to-end analytical pipelines with checkpoint/resume capabilities and activity-based depth adjustment. The core principle: every stage that can fail must be resumable, and every pipeline must adapt its depth to the data it encounters.
When to Use
- Pipeline processes multiple stages sequentially and failures lose hours of work
- Large data corpus requires chunked processing with rate-limit awareness
- Analysis depth should scale with data volume (shallow scan vs. deep dive)
- Long-running jobs need graceful interruption and restart
- Multiple data sources must be combined across stages with partial-failure tolerance
When NOT to use:
- Single-shot scripts under 5 minutes with no intermediate state
- Pipelines where full re-execution is cheap and acceptable
- Real-time streaming (use event-driven patterns instead)
Core Pattern
WITHOUT orchestration: WITH orchestration:
fetch_all_data() --> Stage 1: fetch [checkpoint] -->
process_everything() --> Stage 2: parse [checkpoint] -->
analyze_all() --> Stage 3: enrich [checkpoint] -->
generate_report() Stage 4: report [checkpoint]
Fails at analyze? Fails at enrich?
Start over from scratch. Resume from Stage 3 checkpoint.
Quick Reference
| Concept | Pattern | Anti-pattern |
|---|
| Checkpointing | JSON/pickle per stage, atomic writes | No checkpoints on jobs > 5 min |
| Idempotency | Delete-then-write, upsert by key | Append-only without dedup |
| Depth adjustment | Scale analysis tiers to item count | Hardcoded depth for all inputs |
| Rate limits | Exponential backoff with jitter | Retry loops without delay |
| Progress | Structured log per stage with counts | Silent processing, no visibility |
| Failure handling | Isolate stages, continue on partial | Abort entire pipeline on one error |
Pipeline Architecture
1. Define stages as idempotent units
Each stage reads an input artifact, produces an output artifact, and writes a checkpoint on success. Re-running a completed stage is a no-op.
import json, os, time, hashlib
from pathlib import Path
class PipelineStage:
"""Base for an idempotent, checkpointed pipeline stage."""
def __init__(self, name, checkpoint_dir="checkpoints"):
self.name = name
self.checkpoint_dir = Path(checkpoint_dir)
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
@property
def checkpoint_path(self):
return self.checkpoint_dir / f"{self.name}.json"
def is_complete(self):
"""Check if this stage already finished successfully."""
if not self.checkpoint_path.exists():
return False
cp = json.loads(self.checkpoint_path.read_text())
return cp.get("status") == "complete"
def load_checkpoint(self):
"""Load partial progress for resume."""
if self.checkpoint_path.exists():
return json.loads(self.checkpoint_path.read_text())
{: , : [], : []}
():
tmp = .checkpoint_path.with_suffix()
tmp.write_text(json.dumps(state, indent=, default=))
tmp.rename(.checkpoint_path)
():
.is_complete():
()
._load_output()
state = .load_checkpoint()
already_done = (state.get(, []))
items = [i i input_data ._item_key(i) already_done]
(
)
i, item (items):
:
result = .process_item(item)
state[].append(._item_key(item))
(i + ) % .checkpoint_interval == :
.save_checkpoint(state)
Exception e:
state[].append({
: ._item_key(item),
: (e), : time.time()
})
.save_checkpoint(state)
state[] =
state[] = time.time()
.save_checkpoint(state)
._load_output()
():
NotImplementedError
():
hashlib.md5(json.dumps(item, sort_keys=).encode()).hexdigest()
():
NotImplementedError
checkpoint_interval =
2. Orchestrate stages with a pipeline runner
class Pipeline:
"""Run stages in sequence with checkpoint awareness."""
def __init__(self, stages, checkpoint_dir="checkpoints"):
self.stages = stages
self.checkpoint_dir = Path(checkpoint_dir)
self.log = []
def run(self, initial_input):
data = initial_input
for stage in self.stages:
t0 = time.time()
try:
data = stage.run(data)
elapsed = time.time() - t0
self.log.append({
"stage": stage.name, "status": "complete",
"elapsed_s": round(elapsed, 1),
"items": len(stage.load_checkpoint().get("processed", [])),
"errors": len(stage.load_checkpoint().get("errors", []))
})
except KeyboardInterrupt:
self.log.append({"stage": stage.name, "status": "interrupted"})
print(f"\n[pipeline] Interrupted at {stage.name}. "
f"Re-run to resume from this stage.")
break
except Exception e:
.log.append({
: stage.name, : , : (e)
})
()
data
():
report = ._build_report()
Path(path).parent.mkdir(parents=, exist_ok=)
Path(path).write_text(report)
():
lines = []
lines.append()
lines.append()
lines.append()
lines.append()
entry .log:
lines.append(
)
failures = [e e .log e[] != ]
failures:
lines.append()
f failures:
lines.append(
+ ( f ))
:
lines.append()
lines.append()
.join(lines) +
3. Activity-based depth adjustment
Scale analysis intensity to data volume. Avoid spending deep-analysis resources on tiny datasets, or shallow-scanning massive ones without prioritization.
def choose_depth(item_count, thresholds=None):
"""Select analysis depth tier based on item count.
Returns a dict of parameters that downstream stages consume.
Callers should NOT hardcode depth -- always derive from data.
"""
thresholds = thresholds or {
"shallow": (0, 100),
"standard": (101, 5000),
"deep": (5001, 50000),
"archival": (50001, None),
}
for tier, (lo, hi) in thresholds.items():
if hi is None and item_count >= lo:
return {"tier": tier, "sample_rate": 0.05, "full_analysis": False}
if lo <= item_count <= hi:
return {"tier": tier,
"sample_rate": 1.0 if tier in ("shallow","standard") else 0.2,
"full_analysis": tier in (, )}
{: , : , : }
4. Rate-limit-aware fetching
import random
def fetch_with_backoff(fetch_fn, max_retries=5, base_delay=1.0):
"""Retry with exponential backoff + jitter. Respects rate limits."""
for attempt in range(max_retries):
try:
return fetch_fn()
except RateLimitError:
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f" Rate limited. Waiting {delay:.1f}s (attempt {attempt+1})")
time.sleep(delay)
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(base_delay)
raise RuntimeError(f"Failed after {max_retries} retries")
Handling Insufficient Data
| Situation | Action |
|---|
| Pipeline has nothing to process | Log "no input data", write report noting empty input, exit cleanly with status "no_data" |
| A stage produces empty output | Log warning, pass empty collection to next stage, do NOT skip or error. Let downstream stages handle empty input gracefully |
| External API unavailable | Retry with backoff. After max retries, mark stage as "degraded", continue with cached/partial data if available. Log what was skipped |
| Checkpoint file corrupted | Delete checkpoint, re-run stage from scratch. Log the corruption event |
Common Mistakes
| Mistake | Fix |
|---|
| No checkpointing on jobs > 5 min | Add checkpoint writes every N items and on stage completion |
| Monolithic single-function pipeline | Split into discrete stages with clear input/output contracts |
| Silently skipping failed items | Log every failure with item ID, error, timestamp |
| Hardcoded analysis depth | Use choose_depth() or equivalent based on actual item count |
| Retry without backoff | Always exponential backoff with jitter for external calls |
| Non-atomic checkpoint writes | Write to .tmp then rename (atomic on POSIX) |
| Ignoring partial failures | Collect errors, continue processing, summarize in report |
| No progress visibility | Log stage entry/exit, item counts, elapsed time |
Boundaries
SHOULD do:
- Manage pipeline stage sequencing and dependencies
- Handle interruptions gracefully (Ctrl+C saves checkpoint)
- Scale depth to data size automatically
- Log progress with structured, parseable output
- Produce a status report at
docs/analysis/03-automated-orchestration.md
Should NOT do:
- Make analytical conclusions (that is for domain-specific downstream skills)
- Silently skip failed stages without logging
- Retry indefinitely without backoff
- Decide what data means -- only move it through stages reliably
Report Output
The pipeline MUST end by writing its status to docs/analysis/03-automated-orchestration.md containing:
- Pipeline run timestamp
- Table of stages: name, status (complete/failed/interrupted/skipped), item count, error count, elapsed time
- Section listing any interruptions or skipped steps with reasons
- Summary of depth tier selected and why