Skip to content

feature: honour the prompt header keys the ranker is documented to act on - #217

Merged
Jammy2211 merged 1 commit into
mainfrom
claude/automind-task-planning-8wk4ag
Aug 10, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
claude/automind-task-planning-8wk4ag

Conversation

@Jammy2211

Copy link
Copy Markdown
Contributor

PyAutoMind REFERENCE.md states the contract:

The Intake (Conception) Agent writes these automatically … sourcing Difficulty: from the shared sizing faculty the Feature Agent also uses — so the value shown up front is the one the Feature Agent later acts on.

It was not acted on. _feature.py mentioned Difficulty exactly once, where it printed its own re-derived score, and read no header key at all — not Difficulty:, not Status:, not Priority:, not Blocked-by: / Closes-when:.

The visible failure: feature select recommended draft/feature/autonomy/10_scheduled_runs.md as the next task to start — a prompt declaring Status: blocked, Priority: low and an explicit Blocked-by: gate whose body reads "Do not start before the queue runner has completed several supervised interactive runs cleanly."

Prompt: PyAutoMind:draft/bug/pyautobrain/feature_ranker_ignores_header_keys.md

Where the parsing lives

In the sizing faculty, beside the derivation it overrides — so declared and derived stay in one place, and the bug / refactor conductors (which share parse_prompt) get the same reading for free.

declared_header() reads the five keys and skips fenced blocks. That skip is load-bearing, not tidiness: the bug prompt driving this fix quotes the offending header in a ```-block, so without it the bug report would declare itself blocked. Same rule and same reason as PyAutoMind/lifecycle.py:draft_gate_refs.

Two smaller traps handled: trailing # note comments are stripped by splitting on " #" so a Repo#123 ref survives intact (the live backlog has both on one line), and an unrecognised Difficulty: value is ignored rather than trusted.

What the ranker now does

Key Effect
Difficulty: Overrides the derived level; the disagreement is reported, not silently resolved — the gap is evidence about the heuristic.
Priority: Orders the shortlist, above the difficulty term.
Status: blocked / unresolved Blocked-by: Sinks the prompt below everything; never the recommended pick, and next_action says do not start.

Blocked prompts stay listed in their own labelled band so a human can see and override.

declared_blocked() is deliberately conservative: this agent is offline and cannot tell whether a gate has since closed, so an unresolved Blocked-by: reads as blocked and the output points at lifecycle.py issues --drafts, which does resolve refs against GitHub. Being wrongly held back is cheap and visible; being wrongly recommended is the failure this exists to stop.

Measured on the live backlog

Verification

289 passed (was 279; the 2 test_skill_install.py failures are pre-existing and environment-only — they do not occur in CI).

New tests are hermetic and pin the regression by the offending prompt's header shape, not the live file, so fixing the backlog cannot silently retire them. Confirmed they actually bite: reverting only _feature.py fails 7 of the 10 (the 3 survivors are pure declared_header unit tests, which is coherent). Fixtures use invented repo names, so the file adds no tenant-firewall instance facts — verified against repos_sync.py --check.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WSCA1h6cSKMNwFmWEsxuCL


Generated by Claude Code

…t on

PyAutoMind REFERENCE.md states the contract: Intake persists Difficulty: from
the shared sizing faculty "so the value shown up front is the one the Feature
Agent later acts on". It was not acted on. _feature.py mentioned Difficulty
once, where it PRINTED its own re-derived score, and read no header key at all
— not Difficulty:, not Status:, not Priority:, not Blocked-by:/Closes-when:.

The visible failure: `feature select` recommended a prompt declaring
Status: blocked, Priority: low AND an explicit Blocked-by: gate whose body says
"do not start before the queue runner has run cleanly".

Parsing lands in the sizing faculty, beside the derivation it overrides, so
declared and derived stay in one place and the bug/refactor conductors get the
same reading for free:

- declared_header() reads Difficulty/Status/Priority/Blocked-by/Closes-when,
  skipping fenced blocks. That skip is load-bearing, not tidiness: the bug
  prompt driving this fix QUOTES the offending header in a ```-block, so
  without it the bug report would declare itself blocked. Same rule and same
  reason as PyAutoMind lifecycle.py:draft_gate_refs.
- trailing `# note` comments are stripped, splitting on " #" so a Repo#123 ref
  survives intact (the live backlog has both forms on one line).
- an unrecognised Difficulty: value is ignored rather than trusted.

The ranker then honours them: declared difficulty overrides derived (with the
disagreement REPORTED, not silently resolved — the gap is evidence about the
heuristic); Priority: orders the shortlist above the difficulty term; and a
prompt declaring Status: blocked or an unresolved Blocked-by: sinks below
everything and can never be the recommended pick. It stays listed in its own
labelled band so a human can see and override, and next_action says do not
start.

declared_blocked() is deliberately conservative: this agent is offline and
cannot tell whether a gate has since closed, so an unresolved Blocked-by: reads
as blocked and the output points at `lifecycle.py issues --drafts`, which does
resolve refs against GitHub. Being wrongly held back is cheap and visible;
being wrongly recommended is the failure this exists to stop.

Measured on the live backlog: the blocked prompt drops from rank 1 to last of
27, correctly labelled, and two further blocked prompts surface that had not
been noticed by hand. draft_staleness_detection_signals moves from too-large
#26 to medium #5 — it derived too-large purely on file length, and length is a
bad size proxy because a prompt grows as it accumulates findings.

New tests are hermetic and pin the regression by the offending prompt's HEADER
SHAPE rather than the live file, so fixing the backlog cannot silently retire
them. Verified they actually bite: reverting only _feature.py fails 7 of the
10. Fixtures use invented repo names, so the file adds no tenant-firewall
instance facts.

Prompt: PyAutoMind draft/bug/pyautobrain/feature_ranker_ignores_header_keys.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSCA1h6cSKMNwFmWEsxuCL
@Jammy2211
Jammy2211 merged commit e099383 into main Aug 10, 2026
2 checks passed
Jammy2211 added a commit that referenced this pull request Aug 24, 2026
Third instance of one family: a conductor derives a value from prose and
ignores the header key the author declared. (1) the Feature Agent's ranker,
fixed #217; (2) the Bug Agent; (3) the Intake Agent's Difficulty:.

Intake never read a declared Difficulty at all. It hand-built the dict it
passes the sizing faculty, omitting the declared_header() keys the file-loading
path folds in, so estimate_difficulty ran with no declared key present, nothing
reconciled derived against declared, and the derived level was persisted into
the header — silently replacing what the input asked for. Reported case: raw
text declaring `Difficulty: medium` filed as `too-large` (score 11), unchanged
when the full header block was prepended, hand-corrected after --apply. The
heuristic's biggest input is length, and length is a bad size proxy — a prompt
is long when it carries a design, which is exactly what declaring a level
exists to correct.

Per the prompt, the precedence rule moves INTO the faculty both conductors
already call rather than being re-implemented per conductor — three
reconciliations were three chances to forget one, and two forgot:

- `_sizing.effective_difficulty` is now the single rule: declared wins, derived
  level + score returned alongside so a disagreement is reported, not silently
  resolved. `_feature` drops its local copy and imports it; `_bug` and
  `_intake` now go through it.
- `declared_header` reads `Type:` and `Autonomy:` too (it already read
  Difficulty/Priority/Status/Blocked-by), and `declared_inline` is its
  counterpart for unheadered conception prose — the ideas.md idiom
  "Difficulty large, supervised." included. Neither reads a fenced block: a
  prompt quoting a header documents it, rather than declaring it.
- Intake reads both, keeps the declaration out of the derived title/slug (it
  was leaking into the filename), and honours a declared `Type:` over its prose
  classifier — the filing-note leg, where the prompt naming itself a bug
  throughout was classified `feature`.
- The Bug Agent no longer re-homes a prompt that declares `Type: bug` on prose
  keywords: a defect report legitimately says "refactor" or "documentation"
  while describing a crash.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RXdgVSW8bUhVdEXEz6AAJ
@github-actions
github-actions Bot deleted the claude/automind-task-planning-8wk4ag branch August 25, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants