BVP value leg scores narrative mentions as deliverables (fix + tests)
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 38/100
Direzione di ricerca
Start with agents/termlink/bvp-estimator/estimator.py, especially commitment_text() and the estimate_task() scoring flow; note that commitment sections must be sliced before template scaffolding is stripped. Read and run tests/test_bvp_commitment_scoping.py in the consumer repo, then check the estimator’s existing tests. Done means narrative-only mentions no longer raise scores, real commitments still score, ordering is covered, and legacy bodies remain unchanged.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Context
- Framework version: 1.6.768
- Project: 055-agentic-fleet-cockpit (shared-tooling mode)
- Reported: 2026-09-21T23:03:35Z
Description
Summary
The BVP value leg scores what a task's prose mentions, not what the task
commits to deliver. A task that merely talks about a gate scores the same as
one that builds it — so the score tracks how much prose an author wrote.
This continues T-112 (which stripped template scaffolding). T-113 verified its
own premise, found the boilerplate already gone, and named the remaining defect
without filing it: "the score tracks how much prose someone wrote rather than
how much value the task carries." This is that finding, measured and fixed.
Root cause
Every value scorer decides by trigger-phrase presence over the whole body:
has_gate = _has_any(body, [r"PreToolUse hook", r"completion gate", ...])
if has_gate:
return 4
Run over the entire body, that cannot distinguish BUILDS-a-gate from
MENTIONS-a-gate.
Evidence (measured, not asserted)
1. Correlational — 155 tasks in a live corpus. Longest body quartile scores a
median summed D1–D4 of 3.0; shortest quartile 0.0. Pearson
r(body length, value) = +0.27. Confounded on its own: valuable tasks may
simply get written about more.
2. Causal — controlled mutation, the decisive one. Take a real task; append a
Context paragraph that mentions a completion gate, a PreToolUse hook, a
regression test, an RCA, and L-/G- references, while committing to none of
them:
CONTROL (real task) D1-D4 = (3, 4, 0, 0) sum = 7
MUTANT (+narrative waffle) D1-D4 = (4, 4, 0, 0) sum = 8
Prose that promises nothing bought a point.
Fix
commitment_text() narrows the value leg's input to ## Acceptance Criteria
and ## Verification — the sections where a task states what it will do and how
that will be checked. Narrative sections describe; they do not promise.
Order is load-bearing. strip_template_scaffolding() deletes every column-0
# line — including the ## Acceptance Criteria heading the slice depends on.
Slicing after stripping finds no section, falls through the
backward-compatibility path, and the scoping becomes a silent no-op that still
passes a naive test. My first version had exactly this bug; a dedicated test
now pins the ordering.
Bodies with no commitment sections (legacy tasks) are returned unchanged, so an
older corpus does not silently collapse to zero. That preserves the defect for
those tasks by design — the lesser of two harms, stated rather than hidden.
Results
| before | after | |
|---|---|---|
| mutation delta (waffle) | +1 | 0 |
| genuine gate commitment (D1) | 4 | 4 (unchanged — no false negative) |
| r(body length, value) | +0.271 | +0.068 |
Consumer's full suite: 516 passed, including 4 pre-existing estimator suites
(39 tests) as the regression net.
Limit — please read before merging
Distinct value totals went 10 → 8 and mean 2.30 → 1.46. This repairs the
signal's validity, not its spread. It does not by itself populate Q1,
and it should not be described as "BVP repaired". The remaining degeneracy has a
separate cause still to be characterised.
It is also a behavioural change to scoring semantics for every consumer:
tasks with thin acceptance criteria will score lower. That is arguably correct —
a task that does not say what it will deliver arguably should not score high
value, and G-020 already requires real ACs — but it is a judgement call for the
maintainer, not one a downstream consumer should make unilaterally.
Patch
diff --git a/agents/termlink/bvp-estimator/estimator.py b/agents/termlink/bvp-estimator/estimator.py
index d366afd..7839bf3 100644
--- a/agents/termlink/bvp-estimator/estimator.py
+++ b/agents/termlink/bvp-estimator/estimator.py
@@ -288,6 +288,57 @@ def strip_template_scaffolding(body: str) -> str:
return "\n".join(kept)
+# ── Commitment scoping (value leg) ────────────────────────────────────────────
+_COMMITMENT_HEADINGS = ("acceptance criteria", "verification")
+
+
+def commitment_text(body: str) -> str:
+ """Keep only the sections in which a task COMMITS to deliver something.
+
+ The value scorers (score_d1 … score_d4) decide by trigger-phrase presence:
+ `_has_any(body, [r"PreToolUse hook", r"completion gate", ...])` -> 4. Run
+ over the whole body, that cannot distinguish a task that BUILDS a gate from
+ one that merely MENTIONS gates while narrating background — so the score
+ tracks how much prose an author wrote rather than how much value the task
+ carries.
+
+ Measured on this corpus before the change (155 tasks): the longest quartile
+ of task bodies scored a median summed D1-D4 of 3.0 against 0.0 for the
+ shortest, and a controlled mutation — appending a Context paragraph that
+ mentions mechanisms but promises none of them — raised a real task's D1 from
+ 3 to 4. Prose that commits to nothing bought a point.
+
+ `## Acceptance Criteria` and `## Verification` are where a task states what
+ it will actually do and how that will be checked. Narrative sections
+ (`## Context`, `## Findings`, `## Decisions`, `## Updates`, `## Evolution`,
+ `## Recommendation`) describe, they do not promise.
+
+ Backward compatibility: a body with no commitment section at all (legacy
+ tasks predating the template) returns unchanged, so an older corpus does not
+ silently collapse to zero. That fallback preserves the defect for those
+ tasks by design — it is the lesser of two harms, and it is visible here
+ rather than hidden.
+ """
+ lines = body.splitlines()
+ kept: list[str] = []
+ in_commitment = False
+ found_any = False
+ for ln in lines:
+ m = re.match(r"^(#{2,3})\s+(.*)$", ln)
+ if m:
+ heading = m.group(2).strip().lower()
+ if m.group(1) == "##":
+ in_commitment = any(heading.startswith(h)
+ for h in _COMMITMENT_HEADINGS)
+ found_any = found_any or in_commitment
+ # A `###` under a commitment heading (### Agent / ### Human) stays.
+ if in_commitment:
+ kept.append(ln)
+ if not found_any:
+ return body
+ return "\n".join(kept)
+
+
def score_d1_antifragility(fm: dict, body: str, tags: list[str]) -> tuple[int, list[str]]:
"""D1 — Antifragility: failure-class detection/prevention mechanisms."""
ev: list[str] = []
@@ -2316,6 +2367,13 @@ def estimate_task(task_path: Path, drivers: dict[str, int]) -> dict:
# resolving components from body text: the cost leg then read the very
# boilerplate this strips. estimate_cost now strips too — keep the two in
# step.
+ # Value leg scores COMMITMENTS, not narrative: a task that merely mentions
+ # a gate must not score as one that builds it.
+ # ORDER IS LOAD-BEARING: commitment_text() slices on `##` headings, and
+ # strip_template_scaffolding() deletes every column-0 `#` line — headings
+ # included. Slicing after stripping finds no sections, silently falls back
+ # to the whole body, and the scoping becomes a no-op. Slice first, then strip.
+ value_body = strip_template_scaffolding(commitment_text(body))
body = strip_template_scaffolding(body)
tags = list(fm.get("tags") or [])
is_inception = (fm.get("workflow_type") or "").lower() == "inception"
@@ -2392,9 +2450,9 @@ def estimate_task(task_path: Path, drivers: dict[str, int]) -> dict:
if is_inception:
sc, ev = _score_inception_voi(fm, body, tags)
elif driver_id in handlers:
- sc, ev = handlers[driver_id](fm, body, tags)
+ sc, ev = handlers[driver_id](fm, value_body, tags)
elif name_aliases.get(driver_id) in handlers:
- sc, ev = handlers[name_aliases[driver_id]](fm, body, tags)
+ sc, ev = handlers[name_aliases[driver_id]](fm, value_body, tags)
else:
sc, ev = score_free_driver(driver_id, fm, body, tags)
scores[driver_id] = sc
Regression test
Five tests, all of which fail against the pre-fix estimator. Full file at
tests/test_bvp_commitment_scoping.py in the consumer repo:
test_narrative_that_promises_nothing_does_not_raise_the_score— the mutation, as a standing checktest_a_real_commitment_still_scores— guard against a fix that zeroes everything and passes the above triviallytest_trigger_phrases_in_acceptance_criteria_outscore_the_same_words_in_prose— identical vocabulary, different commitment, different scoretest_commitment_slice_must_be_taken_before_scaffolding_strip— pins the silent-no-op ordering bugtest_a_body_with_no_commitment_sections_is_left_alone— backward compatibility asserted, not assumed
Reported via fw upstream report from field installation
- Lingua principale
- Shell
- Stelle
- 16
- Fork
- 6
- Merge medio
- 6h 24m
- PR unite (30g)
- 5
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di DimitriGeelen/agentic-engineering-framework
-
area:cli bug severity:low status:fixed-on-bleeding-edge triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
DimitriGeelen/agentic-engineering-framework#100 · 5 commenti ·
-
area:cli bug severity:medium status:fixed-on-bleeding-edge triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
DimitriGeelen/agentic-engineering-framework#97 · 5 commenti ·
-
area:context bug severity:low status:fixed-on-bleeding-edge triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
DimitriGeelen/agentic-engineering-framework#96 · 5 commenti ·
-
area:docs bug severity:medium status:in-progress triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
DimitriGeelen/agentic-engineering-framework#95 · 4 commenti ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
Tutte le issue di DimitriGeelen/agentic-engineering-framework
Issue simili
-
TLS guide repeats certs/ in the certificate directoryForse già presa @maheshrijal l’ha presa oggi. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 92/100
prometheus/docs#3156 ·
I maintainer di solito rispondono entro 2 giorni
-
triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
fullsend-ai/agents#1786 · 2 commenti · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
osam-cologne/archlinux-proaudio#917 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
-
security
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
IBM/bob-oic-tutorials#24 ·
I maintainer di solito rispondono entro 1 giorno