Repository navigation
fix: reconcile declared vs derived in the sizing faculty (#274) - #275
Merged
Merged
Conversation
The Intake Agent sized every idea with the shared sizing heuristic and wrote
that estimate into the prompt's `Difficulty:` header, silently discarding a
difficulty the author had already stated — the ideas.md house style ends a
bullet with "Difficulty large, supervised." Worse, the declaration was prose,
so it was swallowed by the derived title and leaked into the slug/filename.
A human who has thought about the work is better evidence than a keyword
heuristic that only sees words, so a declaration now wins:
- `_declared_fields` reads Difficulty/Autonomy/Priority from the raw text,
including the trailing-autonomy idiom ("Difficulty large, supervised.") and
spelling variants ("Too Large", "human required").
- Code fences and inline spans are masked first, so a prompt quoting a
`Difficulty:` line (a repro, a transcript) does not re-size itself.
- The IntakeDecision reports provenance (difficulty_source / autonomy_source /
priority_source, plus the estimate it overrode) and flags the override in the
risks block, so the dry-run review sees both numbers.
- The declaration clause is stripped from the derived title/slug only; the
prompt body stays verbatim.
- `formalise` applies the same precedence.
The sizing faculty is unchanged: it stays the pure estimator, and the override
lives in the agent that owns the header.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RXdgVSW8bUhVdEXEz6AAJ
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
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Closes #274. 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, over a declared
Type: bug; (3) the Intake Agent, over a declaredDifficulty:.Intake never read a declared difficulty at all.
analyse()hand-built the prompt-shaped dict it passes the sizing faculty, omitting thedeclared_header()keys the file-loading path folds in — soestimate_difficultyran 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 declaringDifficulty: mediumfiled astoo-large(score 11), unchanged when the full header block was prepended, hand-corrected after--apply.That matters because the heuristic's biggest input is prompt length, which is a bad size proxy — a prompt is long when it carries a design and the sibling tasks' traps, not when the work is large. Declaring a level is the documented escape hatch for exactly that inversion (
complete/2026/08/feature-ranker-ignores-header-keys.md), and on this path it did nothing.Per the prompt, the precedence rule moves into the sizing faculty — the one place all three conductors already call — rather than being re-implemented per conductor. Three reconciliations were three chances to forget one, and two forgot.
API Changes
_sizing.effective_difficulty(p)— the single precedence rule (moved from_feature.py): declared wins, derived level + score returned alongside so a disagreement is reported rather than silently resolved._sizing.declared_headernow also readsType:andAutonomy:; new_sizing.declared_inlinereads the same keys out of unheadered conception prose, and_sizing.strip_declarationskeeps a declaration out of a derived title._feature.effective_difficultyis gone — imported from the faculty instead. Behaviour identical.difficulty_declared/difficulty_derived/difficulty_disagreement; IntakeDecision gains those plusdifficulty_source/work_type_source/autonomy_source/priority_source.See full details below.
Test Plan
pytest tests/ -q→ 491 passed (15 new intests/test_declared_header_precedence.py)repos_sync.py --check --only "tenant firewall (organ code)"→ OKintake classify --fileon the bug prompt now reportsWork-type: bug (declared)andDifficulty: medium (declared)— it previously classified itfeatureand re-sized itintake "… Difficulty large, supervised."(theideas.mdidiom) →large (declared; heuristic derived small), and the clause no longer leaks into the title or filenamepyauto-brain feature/bugon the same prompt report the declared level with the derived one alongsideFull API Changes (for automation & release notes)
Added
_sizing.effective_difficulty(p) -> (level, score, factors, derived_level)— declared-wins precedence overestimate_difficulty._sizing.declared_inline(text) -> (fields, spans)— declarations in unheadered prose (Difficulty: mediummid-sentence; theideas.mdidiomDifficulty large, supervised.). Code fences and inline spans are masked first, matchingdeclared_header— a prompt quoting a header is documenting it, not declaring it._sizing.strip_declarations(text, spans)— declaration-free text for title derivation only; prompt bodies stay verbatim._sizing.AUTONOMY_LEVELS.Changed
_sizing.declared_headerreturnsdeclared_typeanddeclared_autonomyalongside the existing keys;_HEADER_KEY_REwidened totype|autonomy._bug.analyse_bug/_bug.select_bugsize througheffective_difficulty;_bug.re_home_checkreturnsNonewhen the prompt declaresType: bug(a defect report legitimately says "refactor" or "documentation" while describing a crash)._intake.analysereads both declaration sources (header lines win a tie), sizes througheffective_difficulty, honours a declaredType:over the prose classifier, and derives its title from the declaration-stripped text._intake._derive_fields(theformalisepath) takes the same route.Removed
_feature.effective_difficulty— moved to the faculty and imported (_feature.effective_difficultystill resolves through the star-free import, so callers are unaffected).Migration
Gate
Heart is not reachable from this session (
web-github; PyAutoHeart is not among the session's repos), soship_library's documented fallback applies: the repo's own suite is the gate — 491 passed, plus the tenant-firewall leg CI also runs.Generated by the PyAutoLabs agent workflow.
Generated by Claude Code