fix(missions): harvest before the checkout, and stop an unreachable judge failing done work
deploy / test (push) Successful in 4m27s
deploy / build (push) Successful in 5m26s

Two bugs from the first real Continuous Research run, both found by running it.

**1. The harvest ran AFTER the checkout.** `on_launch` cloned the vault and then
harvested, so the mission's working copy predated the manifest push. The reader
agent found no `harvest.jsonl` and — being resourceful — queried arXiv itself
and wrote its own. That is exactly what `skills/research/arxiv-daily.md`
forbids: the papers it found are not checked off in `corpus_items`, so the next
run re-offers them, while the 13 the real harvest DID shelve went unread. The
harvest now runs first, so the clone contains the manifest.

The analysis it produced was otherwise very good — it named
`crates/clawhdf5-ann/src/hnsw.rs`, cited the ROADMAP's serial insert loop and
proposed a concrete pre-build probe — which is the behaviour the whole design
is for. It was reading the wrong papers.

**2. An unreachable judge consumed a pass.** `Verdict.error` exists to
distinguish "could not judge" from "judged incomplete" and nothing acted on it.
glm-5.3 returned "transport error: error decoding response body", the phase
counted it as a failed pass, and with two budgeted that single outage failed a
phase whose work was done and committed. The evaluator was right to refuse a
same-family fallback — that would trade independence for availability — so the
fix belongs here: an unreachable judge no longer spends an iteration.

Retrying forever would trade a wrong failure for an invisible hang, so the wait
is bounded by `judge_blocked_since` (migration 0078), mirroring how
`capacity_blocked_since` bounds a phase waiting on a VM slot. Thirty minutes is
many sweep ticks, so a blip recovers inside it; past that the phase FAILS with
the transport reason rather than requeueing, because re-running spends a
container re-doing work that was never the problem.

346 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-17 18:14:29 -07:00
co-authored by Claude Opus 5
parent a02e0cba69
commit d524107b37
3 changed files with 165 additions and 20 deletions
+29 -19
View File
@@ -54,25 +54,16 @@ pub async fn on_launch(
return Err("mission not found".into());
};
// ensure_checkout is idempotent (fetch+reset on existing clones,
// clone on missing dirs) so we run it BEFORE the team_id short-
// circuit: a re-launched or retried mission still needs a fresh
// repo checkout even though its team was minted on the first
// launch. Non-fatal — logs and continues on failure.
match crate::mission_workspace::ensure_checkout(pool, workspace_id, mission_id).await {
Ok(Some(path)) => eprintln!(
"mission_orchestrator: repo checked out at {} for mission {mission_id}",
path.display()
),
Ok(None) => eprintln!(
"mission_orchestrator: mission {mission_id} has no repo bound, skipping checkout"
),
Err(e) => eprintln!(
"mission_orchestrator: repo checkout for {mission_id} failed (continuing): {e}"
),
}
// A Continuous Research mission harvests BEFORE its agents start.
// A Continuous Research mission harvests BEFORE its checkout is taken.
//
// The ORDER here is load-bearing and was wrong: the harvest ran after
// `ensure_checkout`, so the mission cloned the vault before the manifest
// was pushed to it. The reader agent found no harvest.jsonl, and — being
// resourceful — queried arXiv itself and wrote its own. That is precisely
// what `skills/research/arxiv-daily.md` forbids: the papers it found are
// not checked off in `corpus_items`, so the next run re-offers them, and
// the 13 the real harvest DID shelve went unread. Harvest first, then
// clone, so the checkout contains the manifest.
//
// Finding papers is not agent work: `library::run_to_vault` searches arXiv,
// checks the `corpus_items` seen-set, fetches and verifies each PDF, shelves
@@ -115,6 +106,25 @@ pub async fn on_launch(
}
}
// ensure_checkout is idempotent (fetch+reset on existing clones,
// clone on missing dirs) so we run it BEFORE the team_id short-
// circuit: a re-launched or retried mission still needs a fresh
// repo checkout even though its team was minted on the first
// launch. Non-fatal — logs and continues on failure.
match crate::mission_workspace::ensure_checkout(pool, workspace_id, mission_id).await {
Ok(Some(path)) => eprintln!(
"mission_orchestrator: repo checked out at {} for mission {mission_id}",
path.display()
),
Ok(None) => eprintln!(
"mission_orchestrator: mission {mission_id} has no repo bound, skipping checkout"
),
Err(e) => eprintln!(
"mission_orchestrator: repo checkout for {mission_id} failed (continuing): {e}"
),
}
// Provision the per-mission ZeroClaw runtime container (C3).
// Idempotent: returns the endpoint if the container is already
// running. Falls back silently when docker is unreachable so