Fix run_recipe endpoint to use content-addressable run_id

The run_recipe endpoint was still using uuid.uuid4() instead of
compute_run_id(). Now it:
- Computes deterministic run_id from inputs + recipe
- Checks L1 cache before running
- Checks L2 and pulls from IPFS if needed
- Only runs Celery if output not found

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-09 11:12:52 +00:00
parent e4b9657d1b
commit ebb692ee94

View File

@@ -1534,8 +1534,6 @@ async def run_recipe(recipe_id: str, request: RecipeRunRequest, ctx: UserContext
# Build DAG from recipe
dag = build_dag_from_recipe(yaml_config, request.inputs, recipe)
# Create run
run_id = str(uuid.uuid4())
actor_id = ctx.actor_id
# Collect all input hashes
@@ -1544,12 +1542,71 @@ async def run_recipe(recipe_id: str, request: RecipeRunRequest, ctx: UserContext
if fixed.content_hash:
all_inputs.append(fixed.content_hash)
# Compute content-addressable run_id
run_id = compute_run_id(all_inputs, f"recipe:{recipe.name}")
output_name = f"{recipe.name}-{run_id[:8]}"
# Check L1 cache first
cached_run = await database.get_run_cache(run_id)
if cached_run:
output_hash = cached_run["output_hash"]
if cache_manager.has_content(output_hash):
logger.info(f"run_recipe: Cache hit for run_id={run_id[:16]}...")
return RunStatus(
run_id=run_id,
status="completed",
recipe=f"recipe:{recipe.name}",
inputs=all_inputs,
output_name=output_name,
created_at=cached_run.get("created_at", datetime.now(timezone.utc).isoformat()),
completed_at=cached_run.get("created_at", datetime.now(timezone.utc).isoformat()),
output_hash=output_hash,
username=actor_id,
provenance_cid=cached_run.get("provenance_cid"),
)
# Check L2 if not in L1
l2_server = ctx.l2_server
try:
l2_resp = http_requests.get(f"{l2_server}/assets/by-run-id/{run_id}", timeout=10)
if l2_resp.status_code == 200:
l2_data = l2_resp.json()
output_hash = l2_data.get("output_hash")
ipfs_cid = l2_data.get("ipfs_cid")
if output_hash and ipfs_cid:
logger.info(f"run_recipe: Found on L2, pulling from IPFS")
import ipfs_client
legacy_dir = CACHE_DIR / "legacy"
legacy_dir.mkdir(parents=True, exist_ok=True)
recovery_path = legacy_dir / output_hash
if ipfs_client.get_file(ipfs_cid, str(recovery_path)):
cache_manager._set_content_index(output_hash, output_hash)
cache_manager._set_ipfs_index(output_hash, ipfs_cid)
await database.save_run_cache(
run_id=run_id, output_hash=output_hash,
recipe=f"recipe:{recipe.name}", inputs=all_inputs,
ipfs_cid=ipfs_cid, provenance_cid=l2_data.get("provenance_cid"),
actor_id=actor_id,
)
return RunStatus(
run_id=run_id, status="completed",
recipe=f"recipe:{recipe.name}", inputs=all_inputs,
output_name=output_name,
created_at=datetime.now(timezone.utc).isoformat(),
completed_at=datetime.now(timezone.utc).isoformat(),
output_hash=output_hash, username=actor_id,
provenance_cid=l2_data.get("provenance_cid"),
)
except Exception as e:
logger.warning(f"run_recipe: L2 lookup failed: {e}")
# Not cached - run Celery
run = RunStatus(
run_id=run_id,
status="pending",
recipe=f"recipe:{recipe.name}",
inputs=all_inputs,
output_name=f"{recipe.name}-{run_id[:8]}",
output_name=output_name,
created_at=datetime.now(timezone.utc).isoformat(),
username=actor_id
)
@@ -3652,6 +3709,7 @@ def render_page(title: str, content: str, actor_id: Optional[str] = None, active
<a href="/runs" class="pb-3 px-1 font-medium transition-colors {runs_active}">Runs</a>
<a href="/recipes" class="pb-3 px-1 font-medium transition-colors {recipes_active}">Recipes</a>
<a href="/media" class="pb-3 px-1 font-medium transition-colors {media_active}">Media</a>
<a href="/download/client" class="pb-3 px-1 font-medium transition-colors text-gray-400 hover:text-white ml-auto" title="Download CLI client">Download Client</a>
</nav>
<main>
@@ -3720,6 +3778,7 @@ def render_ui_html(actor_id: Optional[str] = None, tab: str = "runs") -> str:
<a href="/runs" class="pb-3 px-1 font-medium transition-colors {runs_active}">Runs</a>
<a href="/recipes" class="pb-3 px-1 font-medium transition-colors {recipes_active}">Recipes</a>
<a href="/media" class="pb-3 px-1 font-medium transition-colors {media_active}">Media</a>
<a href="/download/client" class="pb-3 px-1 font-medium transition-colors text-gray-400 hover:text-white ml-auto" title="Download CLI client">Download Client</a>
</nav>
<div id="content" hx-get="{content_url}" hx-trigger="load" hx-swap="innerHTML">