fix(research): the manifest belongs to the mission, and a brittle phrase must not mean silence
deploy / test (push) Successful in 4m28s
deploy / build (push) Successful in 5m21s

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:
Omar Sobh
2026-08-18 07:18:11 -07:00
co-authored by Claude Opus 5
parent 2cd0872e50
commit 850f11838b
4 changed files with 137 additions and 41 deletions
+1 -31
View File
@@ -177,37 +177,7 @@ pub async fn run_to_vault(
git(&vault, &["checkout", "-B", &branch]).await?;
// The run manifest, beside the notes.
//
// `templates/teams/continuous_research.toml` has told its reader role to
// start from `ContinuousResearch/<date>/harvest.jsonl` since it was
// authored, and nothing wrote it — the agents were pointed at a file that
// did not exist. This is the code producing what the prompt already
// promises, so "nothing new today" is a fact the agent reads rather than a
// conclusion it guesses from an empty folder.
//
// Written only when the run is attributed to a mission: a plain library run
// has no agent waiting on it and does not need the extra file in the vault.
let mut staged: Vec<&str> = vec!["60 Papers"];
let manifest_rel = crate::continuous_research::manifest_path(&crate::continuous_research::today());
if mission_id.is_some() {
let manifest_abs = vault.join(&manifest_rel);
if let Some(parent) = manifest_abs.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("create {}: {e}", parent.display()))?;
}
let body = crate::continuous_research::manifest_lines(
&total.papers,
&crate::continuous_research::today(),
);
std::fs::write(&manifest_abs, format!("{body}\n"))
.map_err(|e| format!("write {}: {e}", manifest_abs.display()))?;
staged.push("ContinuousResearch");
}
let mut add: Vec<&str> = vec!["add", "--"];
add.extend(staged);
git(&vault, &add).await?;
git(&vault, &["add", "--", "60 Papers"]).await?;
let message = format!(
"library: {} new paper(s)\n\n{}\n\nShelved in the blob store; this commit is the catalogue.",
total.shelved.len(),