-
Notifications
You must be signed in to change notification settings - Fork 20
refactor(experimentalist): resolve every loop seam as a named component #1313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
18bba1a
refactor(experimentalist): a runner and a context, so the loop is jus…
sklinglernv 2d0262c
refactor(experimentalist): a Candidate is metadata and an artifact re…
sklinglernv 8cebf4a
refactor(experimentalist): stop stripping metadata.json from candidat…
sklinglernv fdebf05
fix(experimentalist): the loop's own prompts still read dropped Candi…
sklinglernv 1c5d366
fix(experimentalist): the evolution tree is keyed by id, so finalize …
sklinglernv e377dc1
fix(experimentalist): the mirror's ancestor lookup used an id as a label
sklinglernv 1ec152f
fix(experimentalist): a fork must not inherit the ancestor's architec…
sklinglernv b78246a
refactor(experimentalist): resolve the model tiers once and hand them…
sklinglernv 358bc2a
fix(experimentalist): keep a candidate's record and its artifact in step
sklinglernv 1d0012f
fix(experimentalist): carry M0's later fixes into the refactored loop
sklinglernv 1611f59
test(experimentalist): prove a method-level model tier reaches its model
sklinglernv 82b4398
refactor(experimentalist): the Builder owns the candidate span, and c…
sklinglernv 4864b47
fix(experimentalist): read the benchmark's winner through the store, …
sklinglernv 038a136
fix(experimentalist): make the resume path survive being interrupted
sklinglernv 194123e
refactor(experimentalist): discard candidates by marking them, not de…
sklinglernv e5a1343
refactor(experimentalist): review renames and clarifications
sklinglernv 3205e94
refactor(experimentalist): the baseline is just the candidate with no…
sklinglernv 70cc3a9
feat(experimentalist): selection becomes a component
sklinglernv 6893fa4
feat(experimentalist): every seam the strategy delegates to resolves …
sklinglernv c21fb80
test(experimentalist): M1's acceptance bar, as an assertion
sklinglernv 31cea74
feat(experimentalist): evaluation resolves by name, and its type stop…
sklinglernv fb28015
feat(experimentalist): prove the plugin mechanism from outside the re…
sklinglernv 86979f7
fix(experimentalist): the round budget is the loop's, and a bad propo…
sklinglernv 97f5b51
fix(experimentalist): make the seams swappable in fact, not only in l…
sklinglernv 95b3812
fix(experimentalist): stop discarded candidates leaking, and removed …
sklinglernv cb53298
fix(experimentalist): candidate identity survives serialization, and …
sklinglernv 0a82a84
fix(experimentalist): the seams a review found still half-connected
sklinglernv 70958b3
fix(experimentalist): two of the previous commit's fixes were wrong
sklinglernv 4fb45d5
docs(experimentalist): say what the slim guard is worth, and pin desc…
sklinglernv 2181850
refactor(experimentalist): one naming rule for config, one for roles
sklinglernv 964d91e
feat(experimentalist): every role declares its entry point, in terms …
sklinglernv 8c02121
fix(experimentalist): the analyzer was built without the run's platfo…
sklinglernv 4c9d967
fix(experimentalist): a Candidate's identity and its measurements sto…
sklinglernv 8efcff5
fix(experimentalist): tell the scorer which round its analysis describes
sklinglernv c09e151
fix(experimentalist): smoke-check the tasks a change claims to fix
sklinglernv 5290343
Merge branch 'main' into modular-experimentalist-m1
sklinglernv 07ee52d
fix(experimentalist): the run's metric contract must reach the compon…
sklinglernv 92f9ab3
test(experimentalist): stop stubbing a method that no longer exists
sklinglernv 3ca96f3
fix(experimentalist): say what each removed config key became, and co…
sklinglernv 4c6a65b
fix(experimentalist): a repeated optimization_type is not a malformed…
sklinglernv 8274441
refactor(experimentalist)!: name each component for its implementatio…
sklinglernv def7469
fix(experimentalist): restore three main-side changes the merge had r…
sklinglernv a53e75c
Merge branch 'main' into modular-experimentalist-m1
sklinglernv 50a52d0
Merge remote-tracking branch 'origin/main' into modular-experimentali…
sklinglernv d5914fc
fix(experimentalist): restore the run-config values the refactor disc…
sklinglernv 1698701
fix(experimentalist): the skill documented run-config keys the schema…
sklinglernv 9c1ddff
fix(experimentalist): run.json names the winner by label, not by id
sklinglernv ea52d28
fix(experimentalist): resolve survivors by label, as main does
sklinglernv 661ad4f
refactor(experimentalist): review feedback — names, paths, and a comm…
sklinglernv 493e1e3
feat(experimentalist): components read traces through the context, no…
sklinglernv d95d4d2
docs(experimentalist): comments that described code which no longer e…
sklinglernv 4a83d7e
docs(experimentalist): say why OutcomeEvaluator has two bases
sklinglernv bc45ab0
Merge remote-tracking branch 'origin/main' into modular-experimentali…
sklinglernv d955598
Merge remote-tracking branch 'origin/main' into modular-experimentali…
sklinglernv 59e1c7f
refactor(experimentalist): a component's class name is its component …
sklinglernv 549d997
fix(experimentalist): put a stranded licence header back at the top o…
sklinglernv f1924e5
fix(experimentalist): carry two cross-plugin changes into nemo-eval-a…
sklinglernv 3afbb63
style(eval-author): sort the import block the rename disturbed
sklinglernv 69def66
feat(experimentalist): a component validates its own settings
sklinglernv e43e6a9
fix(experimentalist): seven findings from automated review
sklinglernv 44260cd
fix(experimentalist): four findings from the second review round
sklinglernv 2e79289
Merge branch 'main' into modular-experimentalist-m1
sklinglernv f482d50
test(experimentalist): cast to Any, not to "Any"
sklinglernv 8d8b6ee
fix(experimentalist): the out-of-tree proof never ran
sklinglernv d61ec07
build(experimentalist): install the out-of-tree example so its tests run
sklinglernv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
plugins/nemo-experimentalist/examples/acme-strategies/acme_strategies/random_search.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """A strategy written outside the Experimentalist repository. | ||
|
|
||
| Everything it uses is public: the entities, the `Strategy` role, and the context | ||
| Protocol. It never imports a private module, and it is selected with | ||
| `strategy: random-search` in the run config. | ||
|
|
||
| The search itself is deliberately trivial. What it demonstrates is that a package | ||
| installed beside the plugin can fill the strategy role, reach the platform only through | ||
| the context, and land results in Studio like any other run. | ||
| """ | ||
|
|
||
| import random | ||
| from pathlib import Path | ||
|
|
||
| from nemo_experimentalist_plugin.config import EvolutionaryOptimizerConfig | ||
| from nemo_experimentalist_plugin.entities import Candidate, Proposal | ||
| from nemo_experimentalist_plugin.experimentalist.roles import Strategy | ||
| from nemo_experimentalist_plugin.experimentalist.seam import StrategyContext | ||
|
|
||
| #: Reuses the code-change Proposal kind, so the built-in Coder can build what this emits. | ||
| CODE_CHANGE = "code-change" | ||
|
|
||
| _IDEAS = [ | ||
| "add a retry around the tool call that fails most often", | ||
| "split the single prompt into a plan step and an execute step", | ||
| "cache the lookup the agent repeats within one task", | ||
| ] | ||
|
|
||
|
|
||
| def _validation_score(candidate: Candidate) -> float: | ||
| """Mean of whatever the evaluator measured on the validation channel. | ||
|
|
||
| Reads `metrics`, not `summary`: `summary` is an optional scalar rollup that nothing | ||
| currently writes, so ranking on it scores every candidate zero. | ||
| """ | ||
| metrics = candidate.rewards["validation"].metrics | ||
| return sum(metrics.values()) / len(metrics) if metrics else 0.0 | ||
|
|
||
|
|
||
| class RandomSearch(Strategy): | ||
| """Propose a random change each round and keep whatever scores best.""" | ||
|
|
||
| name = "random-search" | ||
| supports_resume = True | ||
|
|
||
| def __init__( | ||
| self, | ||
| working_dir: Path, | ||
| config: EvolutionaryOptimizerConfig | None = None, | ||
| framework_skills_dirs: list[Path] | None = None, | ||
| **_: object, | ||
| ) -> None: | ||
| """The arguments the runner constructs a strategy with; the rest are ignored. | ||
|
|
||
| Spelled out rather than left untyped because this package is the worked example a | ||
| third party copies: the signature is the contract, so it should show it. | ||
| """ | ||
| del working_dir, framework_skills_dirs | ||
| settings = config or EvolutionaryOptimizerConfig() | ||
| self._rounds = settings.max_rounds | ||
| self._per_round = settings.max_candidates | ||
| self._builder = settings.builder | ||
|
|
||
| async def run(self, ctx: StrategyContext) -> Candidate | None: | ||
| """Import the agent, then build and score random variants of it.""" | ||
| population = await ctx.candidates() | ||
| if not population: | ||
| population = [await self._import_baseline(ctx)] | ||
|
|
||
| rng = random.Random(0) | ||
| # Resume where the store left off. `supports_resume` is true, so `ctx.candidates()` | ||
| # can return work from an earlier attempt; restarting at 1 would rebuild it and | ||
| # report progress that goes backwards. | ||
| done = max((c.generation for c in population), default=0) | ||
| if done >= self._rounds: | ||
| return self._best(population) | ||
| for generation in range(done + 1, self._rounds + 1): | ||
| parent = rng.choice(population) | ||
| for _ in range(self._per_round): | ||
| proposal = Proposal( | ||
| ancestor=parent.id, | ||
| description=rng.choice(_IDEAS), | ||
| kind=CODE_CHANGE, | ||
| payload={"root_cause": "chosen at random", "optimization_type": "add_method", "task_ids": []}, | ||
| ) | ||
| builder = ctx.component("builder", self._builder) | ||
| candidate = await builder.build(ctx, proposal, generation=generation) | ||
| result = await ctx.evaluate(candidate) | ||
| await ctx.record_reward(candidate, channel="validation", result=result) | ||
| population.append(candidate) | ||
| await ctx.report_progress(completed=generation, total=self._rounds, unit="round") | ||
|
|
||
| return self._best(population) | ||
|
|
||
| @staticmethod | ||
| def _best(population: list[Candidate]) -> Candidate | None: | ||
| """Highest validation score, or None when the run produced nothing.""" | ||
| return max(population, key=_validation_score) if population else None | ||
|
|
||
| async def _import_baseline(self, ctx: StrategyContext) -> Candidate: | ||
| """Commit the agent under test unchanged, using the built-in import Builder.""" | ||
| proposal = Proposal(ancestor=None, description="the agent under test", kind="import", payload={}) | ||
| return await ctx.component("builder", "import").build(ctx, proposal, generation=0) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.