Repository navigation
core: include task inputs in the reuse identity of a rollout - #76
Conversation
A rollout answers the task it was given, but neither cache layer recorded which task that was: the summary stored task ids and configuration, and the completion marker stored the harness fingerprint. Editing a task's instructions or its source files under the same task id therefore let resume report the previous answer as a completed result. Both layers now carry the digest of what the agent was asked and what it was given, and both refuse reuse when it no longer matches: - `runmark.inputs_fingerprint` digests the model-visible prompt (already stripped of the answer key and grading criteria by the adapters) plus the source material, in sorted order with the existing `__pycache__`/`.pyc` exclusions. - `mark_complete` records it next to the harness fingerprint; `is_complete` and `why_not` compare it, so `LabAdapter` and `JobBenchAdapter` re-roll instead of returning a stale rollout. - `schedule._resume` compares the summary's record against the current task and re-runs, which also rebuilds that task's library entries. Both checks are opt-in per caller and a marker or summary written before the field existed keeps its previous meaning, so an upgrade does not invalidate in-flight runs. `WorkBuddyAdapter` is left unchanged on purpose: its `task_files` is the whole task directory, answer key included, so its digest needs an explicit `leak_globs` exclusion first. Closes google-research#65
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
|
I'm following this because of #5, which proposes a I read the diff with that in mind. The check in My other question is about adapters that don't exist yet. Since |
|
Heads-up on overlap with #36: both PRs add a digest to the task summary and check it in |
|
Thanks for the careful read — both points are exactly the kind of thing I want settled before this lands. On WorkBuddy: yes, leaving it out was deliberate, not an oversight. On making If a maintainer prefers the required-keyword direction, I'm happy to do it in a follow-up rather than widen this diff. Net: no code change from me on either point — flagging so the decision is explicit rather than silent. |
|
Correction: I test merged #36 and this PR ( |
|
@OUAO-FRANK I verified base
The scheduler reproductions used Python 3.12.13. I found one remaining scheduler integration case. After a task's inputs change, This standalone reproduction uses the real scheduler and demo executor, without model calls: import tempfile
from pathlib import Path
from harness2.core import schedule
from harness2.demo import DemoAdapter, DemoProposer
with tempfile.TemporaryDirectory() as temporary:
adapter = DemoAdapter(Path(temporary))
proposer = DemoProposer(adapter.spec.channels["proposer"])
kwargs = dict(adapter=adapter, prop=proposer,
runs_root=adapter.spec.runs_root)
schedule.run_task("ledger-1", 1, modes=("agg", "seq"), **kwargs)
source = adapter.task_files("ledger-1") / "ledger.csv"
source.write_text("amount\n1.00\n2.00\n")
agg = schedule.run_task("ledger-1", 1, modes=("agg",), **kwargs)
before = adapter.execution_count
seq = schedule.run_task("ledger-1", 1, modes=("seq",), **kwargs)
output = adapter.deliverable_text(
Path(seq.selections["seq"]["chosen_run_dir"])
).strip()
print(agg.status, seq.status, adapter.execution_count - before, output)Actual at head: The summary merge needs to prevent modes from a different input identity from becoming reusable under the new digest. This transition also needs an end-to-end regression. The existing summary tests call These are local offline checks on macOS, not Linux or hosted CI validation. I have not changed your branch. Codex used. |
_write_summary() merged the previous modes and selections unconditionally. When the task inputs changed and only a subset of modes was re-run, the stale modes from the old inputs were re-written under the new digest, so a later run of those modes skipped recomputation. Detect an input change and carry nothing over from the old summary in that case.
|
@RanaPriyansh thanks for the thorough repro — that's a real gap, and you're right that the existing tests only exercised Fixed in inputs_changed = (
res.inputs is not None
and old.get("inputs") is not None
and old.get("inputs") != res.inputs
)
if not inputs_changed:
# merge old modes / selections / errors as before ...
# else: the previous summary answered a different question, so its
# mode records and selections must not be re-used under the new digest.So the
|
Closes #65
What
Task inputs are now part of a rollout's reuse identity, at both cache layers the report names:
runmark.inputs_fingerprint(task_prompt, files_dir)digests the model-visible task text plus the source material, walked in sorted order and hashed by relative path + bytes, reusing the existing_ignoredexclusions (__pycache__,.pyc,.DS_Store). The adapters already strip the answer key and the grading criteria fromtask_prompt, so those never enter the digest.mark_complete(..., inputs=)records it next to the harness fingerprint;is_complete(..., inputs=)andwhy_not(..., inputs=)compare it.LabAdapter.executeandJobBenchAdapter.executepass it, so a rollout whose task changed is re-rolled with a log line naming the reason instead of being returned.schedule._resumecompares the summary's recorded digest with the current task and re-runs, which also rebuilds that task's library entries (Library.addis keyed bytask_id).Contract
Consistent invalidation rather than a new hard error: a mismatch means "not reusable", exactly how a harness-fingerprint mismatch has always been handled, so the existing re-roll path does the work and no new failure mode is introduced.
Both checks are opt-in per caller, and a marker or summary written before the field existed keeps its previous meaning —
_inputs_matchtreats a missinginputskey as "no opinion" — so upgrading does not invalidate every in-flight run. Bumpingrunmark.VERSIONwould have done that; I deliberately did not.WorkBuddyAdapteris unchanged on purpose: itstask_filesis the whole task directory including the answer key, so its digest needs an explicitleak_globsexclusion before it can join. Left as a follow-up rather than guessed at here.cli.run_sweep._check_identityis also untouched: adding a key to that dict would make every existingconfig.jsonmismatch on upgrade. It is a namespace guard, not a reuse gate — the reuse decision is per task, in_resume, which is where the fix belongs.How checked
New file
tests/test_task_input_identity.py, 13 tests, all passing:The three
RolloutReuseTestsdriveLabAdapter.executewith the executor replaced by a deterministic function that writes the current document (no model call, no downloaded benchmark, no shell) — the report's standalone reproduction. At the base committest_changed_source_files_re_roll_the_rolloutandtest_changed_instructions_re_roll_the_rolloutfail (executor calls: 1, stale answer), andtest_unchanged_inputs_reuse_the_rolloutpasses, so the change re-rolls on changed inputs without over-invalidating unchanged ones. The other ten tests cover the digest, the marker, and_resume; they error at base only because the API they exercise does not exist yet.Running the report's standalone LAB snippet by hand at head:
The same snippet at base gives
cached output: version-one/executor calls: 1, i.e. the reported symptom.ruff check .passes andruff format --checkis clean over the touched files.Platform caveat, stated plainly: the report's scheduler reproduction could not be re-run end to end on this machine.
tests.test_workflowandschedule._write_summaryneed POSIXfcntland a shell-driven demo adapter (pyproject.tomldeclaresOperating System :: POSIX), so on my Windows host the existing suite is 4 failures / 1 error at the base commit and at head, with the identical failure names — no new breakage, but also no green baseline to compare against. I verified the summary layer throughschedule._resumedirectly instead, which needs neitherfcntlnor a subprocess. A Linux CI run ofpython -m unittest discover -s testsis the check I could not perform and would like from a maintainer.The cost is one hash pass over a task's source files per rollout and per task summary — small next to the rollout itself, but it is a real cost on JobBench.