Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions agentdescent/evolvespec.py
Original file line number Diff line number Diff line change
Expand Up @@ -1179,10 +1179,8 @@ def run(skill: str, task: Task) -> str:
run=run, propose=reflector(reflect),
strategy=SeededAppendRules(seed=_read_text_target(spec.target)),
blast_radius=SKILL_BLAST_RADIUS)
notes.append(
"kind=text: SeededAppendRules keeps the seed prompt and appends each "
"accepted one-sentence rule under '# Learned rules' (#837); pass "
"strategy=SingleSlot(...) in evolve knobs for full-rewrite semantics")
# The SeededAppendRules note is added after knobs merge, once we know
# whether a card or caller replaced strategy (#879).
# Inline text is supported, but a path-looking missing file is almost
# certainly a typo -- warn so plan surfaces it (#706).
if looks_like_path_target(spec.target) and not os.path.isfile(
Expand Down Expand Up @@ -1348,6 +1346,14 @@ def run(skill: str, task: Task) -> str:
# -- knobs: kind defaults < spec.evolve < caller overrides ------------------
knobs: Dict[str, Any] = {**defaults, **(ov.knobs if ov else {}),
**_evolve_pass_through(spec.evolve), **overrides}
# kind=text defaults to SeededAppendRules above; a card (gepa/ace/dgm/
# skillopt) or compose(strategy=...) puts "strategy" in knobs and then
# overwrites that default. Don't also claim SeededAppendRules (#879).
if spec.kind == "text" and "strategy" not in knobs:
notes.append(
"kind=text: SeededAppendRules keeps the seed prompt and appends each "
"accepted one-sentence rule under '# Learned rules' (#837); pass "
"strategy=SingleSlot(...) in evolve knobs for full-rewrite semantics")
# The dedicated field and the pass-through agree; an explicit `spec.evolve`
# entry wins, exactly as it would for any other knob.
if spec.extra_artifacts:
Expand Down
4 changes: 4 additions & 0 deletions changelog.d/879-text-strategy-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fixed
When a `kind=text` card or caller sets `strategy`, plan no longer also
claims `SeededAppendRules`. `gepa` / `ace` / `dgm` / `skillopt` drop the
generic note; `rrsi` and a bare text spec still show it (#879).
42 changes: 42 additions & 0 deletions tests/test_seeded_append_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

import pytest

from agentdescent.evolution import SeededAppendRules, SingleSlot, Task, evolve
from agentdescent.evolvespec import EvolveSpec, compose
from agentdescent.strategies import rule_id
Expand Down Expand Up @@ -81,3 +83,43 @@ def test_empty_state_preserves_seed_trailing_newline():
"""apply no-op depends on empty render matching the on-disk seed (#849)."""
seed = "original\n"
assert SeededAppendRules(seed=seed).render({}) == seed


_SEEDED_NOTE_PREFIX = "kind=text: SeededAppendRules"


def _text_spec(**extra):
return EvolveSpec(
kind="text",
target=SEED,
data={"inline": [{"prompt": f"q{i}", "gold": "a"} for i in range(8)]},
agent="echo",
reflect="echo",
**extra,
)


def _has_default_seeded_note(notes):
return any(n.startswith(_SEEDED_NOTE_PREFIX) for n in notes)


@pytest.mark.parametrize("algorithm, expect_note", [
(None, True),
("rrsi", True),
("ace", False),
("dgm", False),
("gepa", False),
("skillopt", False),
])
def test_text_seeded_append_note_follows_actual_strategy(algorithm, expect_note):
"""Cards that replace strategy must not also claim SeededAppendRules (#879)."""
extra = {} if algorithm is None else {"algorithm": algorithm}
comp = compose(_text_spec(**extra))
assert _has_default_seeded_note(comp.notes) is expect_note


def test_text_seeded_append_note_drops_when_caller_sets_strategy():
"""A compose(strategy=...) override is the same as a card knob (#879)."""
comp = compose(_text_spec(), strategy=SingleSlot(initial_value=SEED))
assert isinstance(comp.kwargs["strategy"], SingleSlot)
assert not _has_default_seeded_note(comp.notes)
Loading