Commit Graph
5 Commits
Author SHA1 Message Date
Omar SobhandClaude Opus 5 48606f2fe3 fix(triage): a relevance score on a pre-filtered corpus ranks nothing — measure evidence instead
deploy / test (push) Successful in 5m30s
deploy / build (push) Successful in 5m57s
Mission 01a0c940 ran the paper triage live for the first time: 10 papers,
10 tagged, 10 scored — and the relevance scores were 2.93, 2.94, 2.97,
2.97, 2.98, 2.99, 2.99, 2.99, 3.00, 3.00. A spread of 0.07 across a
4-level scale, every answer confident, no ranking information at all. Of
course: the harvest runs the operator's own arXiv topic queries, so every
paper in the file is about agents by construction. Asked on the same ten
abstracts, 'how actionable is it' saturated the same way (spread 0.20).

What separated them was the strength of the evidence behind the claims:
1.36 (a benchmark paper) to 3.00 (measured on real systems with
ablations), spread 1.64 — and what KIND of paper it is (method /
benchmark / measurement / survey / position), with the confidence of that
call beside it so an unplaceable paper reads as unplaceable. The manifest
now carries those two and no relevance number, and arxiv-daily.md tells
the reading agents what each means and why there is no relevance.

The general rule, since this class of mistake is invisible — a saturated
score looks exactly like a working feature: patterns::spread() with
SATURATED_BELOW, and triage_papers warns when a live harvest's scores
span less than that. A question that returns the same number for
everything is a defect in the question, not a fact about the population.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
2026-09-22 08:19:41 -05:00
Omar SobhandClaude Opus 5 33560c7fe6 feat(decide): memory rerank and paper triage on the decision tier
deploy / test (push) Successful in 5m23s
deploy / build (push) Successful in 5m27s
mission_memory::recall is two stages when a key is set: BM25 proposes 8
candidates, one Noul per candidate ('this earlier verdict is relevant to
the task') reorders them and drops those under 0.3, three are kept. BM25
measures keyword overlap, and a verdict about MICROVM.md shares words
with every task that names a file. Without a key the BM25 order stands.

continuous_research: topic_tags was written as [] on every manifest line
since the manifest existed. triage_papers asks, per harvested paper, a
Choice over the mission's topics (readable names, the arXiv query as the
description, 'none' offered) and a four-level relevance Score; the tags
(every topic ≥ 0.3) and {score, confidence} land on the line the agents
already read. Probe on PORTICO's abstract: relevance 3.0 at 1.0; topic
'none' 0.59 / verification 0.41 — true, the topic list has no
authority/sandboxing entry. Untriaged papers write the old empty line.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-21 14:09:28 -05:00
Omar SobhandClaude Opus 5 850f11838b 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]>
2026-08-18 07:18:11 -07:00
Omar SobhandClaude Opus 5 a02e0cba69 feat(missions): Continuous Research harvests at launch, and cards launch by clicking
deploy / test (push) Successful in 4m21s
deploy / build (push) Successful in 5m14s
The card shipped in e20b321 could not actually be used. Three things were
missing, each of which failed at a different distance from its cause.

**1. `default_team_template` was parsed and never read.** Every recipe declares
one; `WorkflowRecipe` carries the field; nothing consumed it. A mission created
from a card with no explicitly chosen team was rejected at LAUNCH with "no
team_id, no team_template_id, no config.phase_teams" — one step removed from the
real cause, which is that creation ignored the recipe. Create now resolves it
via `team_templates::get_by_key`, only when the caller named no team of any
kind, so an explicit choice still wins. A test asserts every shipped recipe
names a template that has a `templates/teams/<key>.toml`, because a mismatch
there produces an unlaunchable card.

**2. The harvest ran nowhere.** `harvest_for_mission` existed and nothing called
it. `on_launch` now runs it for `continuous_research` missions, before the
phases start, and threads the blob store through from `main` (the route already
had it on `AppState`; the scheduler needed it). Deliberately non-fatal: a
harvest that fails still starts the phases, because the phase is what reports
whether today was quiet or broken and those must stay distinguishable — but
never silent, so both outcomes log their counts.

**3. Nothing wrote the manifest.** `templates/teams/continuous_research.toml`
has pointed its reader role at `ContinuousResearch/<date>/harvest.jsonl` since it
was authored, and the file did not exist — agents aimed at a path nothing
produced. `run_to_vault` now writes it beside the notes and stages it, but only
for a mission-attributed run. `Harvest` carries the shelved `Paper`s to build
it; re-parsing the notes we had just written would have been a parse of our own
output and one more place for the two to drift.

Also: the blob root. `storage.data_dir` defaults to "./data" and the container's
cwd is `/`, so the server tried to create `/data` as uid 65532 and EVERY shelve
failed with "storage io: Permission denied". The image now creates
/var/lib/clawmates-blobs owned by 65532 so a mounted volume inherits it rather
than arriving root:root. Kept off /var/lib/clawmates-missions on purpose: that
tree is swept, and a paper shelved there would be deleted out from under its own
catalogue note.

Proven end to end on a real mission: 15 candidates, 2 already held, 13 shelved,
0 failed; branch auto-merged as additive-only; manifest on vault `main` with
every documented key. The "already held" counts are the seen-set deduping across
topics within a single run, which is the behaviour the whole design exists for.

The project brief now comes from the mission description — `phase_task_text`
already places it under BRIEF verbatim, so no new field was needed.

346 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 15:05:37 -07:00
Omar SobhandClaude Opus 5 e20b321055 feat(missions): Continuous Research is a mission type, not just a team checkbox
deploy / test (push) Successful in 4m6s
deploy / build (push) Successful in 5m32s
`templates/teams/continuous_research.toml` has existed with three well-written
roles since it was authored, but no workflow recipe pointed at it — every
recipe in templates/workflows/ defaults `default_team_template = "rust_sdlc"`.
So the only way to reach it was as a checkbox under Advanced. It is now a
Step-1 card: the registry loads it at boot and `GET /api/workflows` serves it,
with no frontend change (MissionWizard renders whatever the endpoint returns).

Both phases are kind `research`, deliberately, rather than new `read`/`script`
kinds. An unrecognised kind falls through `purposes_for`'s `_ => ["mission"]`
and is absent from `PRODUCING_KINDS`, so it would get the generic directive AND
be exempt from the empty-delivery rule — a phase that produces nothing and
still passes. That is the shape this codebase keeps paying for; two `research`
phases differentiated by `task` keep both guards.

`commit_policy = "always"`, not `on_green_tests`: the vault is prose with no
suite, so a test gate would find nothing to run and land every branch `-wip`.

The harvest is NOT an agent phase. `continuous_research.rs` calls the existing
`library::run_to_vault` — arXiv search, seen-set check, PDF shelf, vault note,
attributed by `mission_id` — because that path is deterministic, takes seconds,
and owns the `corpus_items` seen-set that is the whole reason a recurring
mission knows what it already covered. An agent redoing it would be slower and
would lose that.

The manifest path is not invented either: the team template has told
`signal_harvester` to write `ContinuousResearch/<date>/harvest.jsonl` all along.
This makes the code produce what the prompt already promised, and a test pins
the path and every documented key so the two cannot drift into an agent reading
a file nothing writes.

DEFAULT_CORPUS / DEFAULT_VAULT_URL exported rather than duplicated, so the
route and the launch hook cannot disagree about which vault.

344 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-17 14:18:35 -07:00