fix(research): the manifest belongs to the mission, and a brittle phrase must not mean silence
Two defects from the first on-topic run.
**1. The manifest could never be updated twice in a day.** It was written into
the VAULT at a per-DATE path, but it is per-RUN data. A second mission the same
day rewrites a file that already exists, and `auto_merge` correctly refused the
whole branch:
diff is not additive (1 non-add change(s), first:
M ContinuousResearch/2026-08-18/harvest.jsonl); left for a human
So `main` kept the FIRST run's manifest, the next mission cloned it, and the
agents analysed yesterday's papers while every log line reported a successful
harvest. The merge policy was right; the placement was wrong. The manifest now
goes into the mission's own checkout after `ensure_checkout`, which keeps the
vault additive and gives each mission exactly its own papers. The agents commit
it alongside their analysis through the normal delivery path.
**2. A quoted phrase that matches nothing looked like a quiet day.** Phrase
search is precise and brittle: "hybrid retrieval BM25 dense" is a reasonable
topic and appears verbatim in no paper on arXiv — measured, 0 hits — while the
same four terms unquoted return exactly the hybrid-retrieval evaluations the
topic asked for. Harvesting zero because of adjacency is indistinguishable from
a genuinely quiet field, which is the distinction `Harvest::healthy()` vs
`added_anything()` exists to preserve. `search` now retries unquoted when the
phrase finds nothing, and says so in the log.
Proven in one run, all three behaviours at once:
"approximate nearest neighbor search" -> 5 candidates, 5 already held, 0 shelved
"hybrid retrieval BM25 dense" -> no exact phrase match, retrying broad
-> 5 candidates, 0 already held, 5 shelved
"LLM as a judge evaluation" -> 5 candidates, 5 already held, 0 shelved
wrote 5 paper(s) to .../ContinuousResearch/2026-08-18/harvest.jsonl
The seen-set suppressing 10 of 15 is the whole point of a recurring mission, and
the 5 that landed are on topic for the first time: RAG architecture evaluation,
agent-controlled search over chat logs, compute-aware retrieval and reranking,
hybrid retrieval in hyperbolic space, sparse-dense fusion limits.
353 tests pass.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2cd0872e50
commit
850f11838b
@@ -75,6 +75,7 @@ pub async fn on_launch(
|
||||
// because the phase is what reports whether today was quiet or broken, and
|
||||
// those must stay distinguishable. What is never acceptable is silence, so
|
||||
// both outcomes are logged with their counts.
|
||||
let mut harvested: Vec<crate::papers::Paper> = Vec::new();
|
||||
if mission.template_kind == crate::continuous_research::TEMPLATE_KIND {
|
||||
match blobs.as_ref() {
|
||||
Some(b) => {
|
||||
@@ -89,9 +90,13 @@ pub async fn on_launch(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(n) => eprintln!(
|
||||
"mission_orchestrator: continuous research harvest shelved {n} paper(s) for mission {mission_id}"
|
||||
),
|
||||
Ok(papers) => {
|
||||
eprintln!(
|
||||
"mission_orchestrator: continuous research harvest shelved {} paper(s) for mission {mission_id}",
|
||||
papers.len()
|
||||
);
|
||||
harvested = papers;
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
"mission_orchestrator: continuous research harvest FAILED for {mission_id} (phases still start, and will report an empty day): {e}"
|
||||
),
|
||||
@@ -113,10 +118,33 @@ pub async fn on_launch(
|
||||
// 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(Some(path)) => {
|
||||
eprintln!(
|
||||
"mission_orchestrator: repo checked out at {} for mission {mission_id}",
|
||||
path.display()
|
||||
);
|
||||
// The manifest goes in the CHECKOUT, not the vault: it is this run's
|
||||
// input, and the vault path is per-date and shared, so a second run
|
||||
// the same day rewrites a file that already exists and auto_merge
|
||||
// rightly refuses the branch. See `write_manifest`.
|
||||
if mission.template_kind == crate::continuous_research::TEMPLATE_KIND {
|
||||
let date = crate::continuous_research::today();
|
||||
match crate::continuous_research::write_manifest(&path, &harvested, &date) {
|
||||
Ok(at) => eprintln!(
|
||||
"mission_orchestrator: wrote {} paper(s) to {}",
|
||||
harvested.len(),
|
||||
at.display()
|
||||
),
|
||||
// Loud: the reader phase would find no manifest and, being
|
||||
// resourceful, go and search arXiv itself — which corrupts
|
||||
// the seen-set. Better to see why here.
|
||||
Err(e) => eprintln!(
|
||||
"mission_orchestrator: could NOT write the harvest manifest for \
|
||||
{mission_id} — the reader phase will see no papers: {e}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => eprintln!(
|
||||
"mission_orchestrator: mission {mission_id} has no repo bound, skipping checkout"
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user