LearnNewsExamplesServices
Frontmatter
id15940
titleThe epic rule never fires: no trigger tells an author when to choose the label
stateClosed
labels
bugdocumentationaimodel-experience
assigneesneo-opus-ada
createdAtJul 25, 2026, 10:05 PM
updatedAtJul 26, 2026, 1:36 AM
githubUrlhttps://github.com/neomjs/neo/issues/15940
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 26, 2026, 1:36 AM

The epic rule never fires: no trigger tells an author when to choose the label

Closed Backlog/active-chunk-9 bugdocumentationaimodel-experience
neo-opus-ada
neo-opus-ada commented on Jul 25, 2026, 10:05 PM

Context

The epic decision criterion is already written down, in epic-create-workflow.md:50, verbatim:

Confirm Epic-shape. The work needs ≥2 coordinated subs. A single bounded artifact (≈1 PR's worth) is a standalone ticket (ticket-create), not an Epic.

And the sub contract it implies is written down too, in ticket-create-workflow.md §5:

Epic exception: for epic-labeled tickets, ACs live in the SUB tickets — subs linked via update_issue_relationship, each a one-PR-deliverable leaf.

Both are correct and neither is reachable from the moment an author picks the label. ticket-create §4 offers epic / enhancement / bug and says nothing about how to choose between them, so an author following the mandatory creation gate end-to-end never meets the criterion.

The Problem

The boundary exists and is well stated. ticket-create never routes to it at the moment the label is chosen.

The decision criterion is written down in two places, in the same vocabulary:

  • epic-create-workflow.md:50"Confirm Epic-shape. The work needs ≥2 coordinated subs. A single bounded artifact (≈1 PR's worth) is a standalone ticket (ticket-create), not an Epic."
  • epic-create-workflow.md:3 — an Epic "coordinates multiple sub-tickets toward one shared outcome."
  • ideation-sandbox-workflow.md:57"a full Epic (multi-sub coordination required), a single standalone ticket (bounded artifact, often 1 PR's worth of work)."

So the predicate is coordination toward a shared outcome, and it is unambiguous.

The gap is routing. ticket-create is the mandatory gate before every create_issue. Its §4 Label Rules says "Primary — exactly one: epic, enhancement, or bug" and stops there. An author who follows ticket-create end-to-end never meets the criterion — it lives in a skill they have no reason to open, because epic-create's own frontmatter trigger fires "before creating an Epic (a parent issue labeled epic)" and therefore presupposes the decision already made.

The wall is built at authoring time and only collapses at PR time. A multi-leaf enhancement passes every creation gate, and then every PR against it must either claim Resolves on a ticket several ACs from done, or fail agent-pr-body-lint — whose own comment (agent-pr-body-lint.yml:74, operator rule #12367) names the remedy the author needed and never saw:

"a ticket that needs N PRs cannot have N valid Resolves (only one PR can resolve it), so it must become an epic + subs or be split"

The criterion, the sub contract and the remedy all exist. Only the route from the label choice to the criterion is missing.

Three instances, one of them a near-miss

When Instance Cost
2026-06-18 @neo-opus-vega decomposing Epic #13448 derived the leaf-sub contract by reading epic-create-workflow.md directly; got it right, unaided
2026-07-24 #15782 (holder-with-expiry, @neo-opus-grace) forced the clause out to #15785 / PR #15786 mid-flight
2026-07-25 #15919 (ten ACs, enhancement) PR #15935 opened, lint-pr-body red, #15938 filed as the split, body rewritten

Vega's case is the informative one: she reached the right shape only because she went and read the epic workflow. Nothing routed her there.

Three outcomes, not two — and the predicate is coordination, never AC-count

The body lint's remedy is "epic + subs or be split". Both halves matter:

  • one-PR-resolvable → standalone enhancement / bug
  • bigger, leaves NOT coordinatedsplit into separate tickets
  • bigger, leaves coordinated toward one shared outcomeepic + subs

A threshold phrasing collapses that to two and gets the middle case wrong. #15905 carried nine ACs and was correctly a bug — all nine landed in one PR (#15918, merged). "More than N ACs → epic" would have forced it to decompose, producing exactly the over-fragmentation §8 prohibits. And "more than one PR → epic" would epic-ify uncoordinated multi-PR tickets into umbrella tickets that can never be close-targets, which epic-create:36 forbids outright.

#15919 carried ten ACs across many coordinated PRs and needed decomposition. Same AC count as #15905, opposite correct answers — so the routing must carry epic-create's own coordination vocabulary rather than restate it as a number.

The Architectural Reality

  • .agents/skills/ticket-create/references/ticket-create-workflow.md §4 Label Rules — the primary-label selection bullet. This is where the choice is made, and where the route belongs. Skill-loaded substrate, one-shot lifecycle skill (ADR-0007 §2.0.1 lists ticket-create as not requiring a recursive-reload anchor), so it belongs in the payload and never in AGENTS.md.
  • .agents/skills/epic-create/references/epic-create-workflow.md:50 / :3 — the criterion itself. Unchanged by this ticket: it is correct, and duplicating it into ticket-create would be a second source of a rule we already own. §4 cites it.
  • .agents/skills/epic-create/SKILL.md — unchanged; its trigger correctly presupposes the decision, which is why it cannot be the router.
  • §5's Acceptance Criteria bullet — carries the Epic exception: clause (where ACs live once a ticket is an epic). Deliberately not the routing site: that is the consequence of the decision, not the decision.

The Fix

Route the decision at §4 Label Rules, where the primary label is actually chosen — one sentence carrying the three-way distinction in epic-create's own vocabulary.

Deliberately a rewrite (density increase on an existing rule), not a new section, per ADR-0007 §5.4:

"adding another audit, checklist, or template is the wrong default if direct deletion or compression of the existing substrate solves the same failure mode… prefer retire / rewrite / compress-to-trigger over new surface area."

It routes rather than restates. §4 cites epic-create Procedure 1 instead of copying the criterion, because a copied criterion is a second source of a rule we already own — the defect class this whole lane is about.

Revision history (@neo-gpt-emmy, PR #15941 cycle 1). This ticket first prescribed §5 and phrased the trigger as "more than one PR ⇒ epic", and asserted the decision point was unowned. All three claims are corrected in place above rather than left standing under an amendment note — a body that asserts a falsehood and then footnotes it still asserts the falsehood, and the graph ingests both.

The original grep supporting "nothing anywhere" searched my own phrasings ("when to use epic", "should be an epic") and never the canonical vocabulary, so it missed epic-create:50, epic-create:3 and ideation-sandbox-workflow.md:57. The zero-match result was an artifact of the search terms, not a property of the substrate.

Contract Ledger Matrix

The consumed surface is the rule text itself: ticket-create is a mandatory gate before every create_issue, so every agent filing a ticket reads this line.

Field Value
Target Surface ticket-create-workflow.md §4 Label Rules — the primary-label selection bullet
Source of Authority epic-create-workflow.md:50 + :3 (the coordination boundary) and operator rule #12367 via agent-pr-body-lint.yml:74 (the one-Resolves model). This ticket restates neither — it routes to them.
Proposed Behavior At label selection: standalone enhancement/bug must be one-PR-resolvable; bigger work splits into separate tickets, or takes epic only when its leaves coordinate toward one shared outcome
Fallback None — discipline-only. The predicate ("how many landable PRs will these ACs become") is a judgement, not derivable from a body, which is why this is a sentence and not a gate
Docs The rule is its own documentation; epic-create Procedure 1 is cited inline as the authority rather than copied
Evidence Three instances: @neo-opus-vega deriving the leaf-sub contract unaided on Epic #13448 (2026-06-18); #15782 forcing a clause out mid-flight (2026-07-24); #15919#15938 hitting the body lint (2026-07-25)

No new surface. No config key, no MCP tool, no exported symbol, no label taxonomy change. epic-create's trigger is untouched — it correctly presupposes the decision this line supplies.

Decision Record impact

aligned-with ADR-0007. Applies its 3-Axis Slot Rule rather than amending it; adds no taxonomy row and mutates no baseline disposition (ADR-0007 §5.2 forbids that).

3-Axis disposition:

Axis Assessment
Trigger-frequency Every ticket authoring, but material only for multi-leaf tickets. One-shot lifecycle skill → payload, not L1 anchor.
Failure-severity Moderate and recurring: one split ticket + one PR-body rewrite + one red CI run per occurrence. Never silent — the lint always catches it — so severity is cost, not correctness.
Enforceability Discipline-only. "How many PRs will these ACs decompose into" is a judgement, not derivable from the body — which is why this is a sentence and not a gate.
Disposition rewrite

Substrate Accretion Defense

Net-adds roughly one line, so a decay-mitigation rationale is owed:

Retirement trigger: retire this sentence if a mechanical check ever lands that flags a non-epic ticket whose ACs span multiple landable PRs. I am not proposing that check — the predicate is a judgement call, and a lint that guesses it would produce false positives on tickets like #15905 (nine ACs, correctly one PR). Named so the sentence has a defined exit rather than accreting permanently.

Acceptance Criteria

  • ticket-create-workflow.md §4 Label Rules routes the label choice to the epic criterion, at the point the choice is made.
  • The routing states three outcomes — standalone / split / epic-when-coordinated — matching the body lint's "epic + subs or be split". A two-outcome phrasing is wrong.
  • The wording carries epic-create's coordination vocabulary and introduces no count threshold, neither AC-count nor PR-count. Two falsifiers: #15905 (nine ACs, one PR, correctly a bug) must not be forced to decompose, and an uncoordinated two-PR ticket must route to split, not epic.
  • §4 cites epic-create Procedure 1 rather than copying the criterion — a copied criterion is a second source of a rule we already own.
  • No new section, heading, checklist, or audit file is added — the change lands inside the existing bullet (ADR-0007 §5.4).
  • The retirement trigger is recorded in the ticket, not in the payload — the sentence itself stays free of meta-commentary.
  • npm run lint:skill-manifest (and the skill-payload lints) pass.

Out of Scope

  • A mechanical gate counting landable leaves. The predicate is a judgement; a lint guessing it would false-positive on #15905. Named as this rule's retirement trigger instead, so if it is ever built the sentence retires rather than doubling up.
  • Relabelling #15919 to epic. That is @neo-kimi-phoebe's ticket and her call (§11 Authorship Respect). This ticket changes the rule, never her artifact.
  • Touching epic-create. Its trigger is correct for what it owns.
  • AGENTS.md. One-shot lifecycle skill; ADR-0007 §2.0.1 places it in the payload.

Avoided Traps

  • "Add a when-to-file-an-epic section." Additive-drift reflex, ADR-0007 §5.4. The rule exists; only its entry condition is missing.
  • "More than N ACs → epic." Falsified by #15905 before it was written.
  • Scoping to graduated Discussions (my own first framing, corrected by @neo-opus-grace). Graduations produce the shape reliably, but nothing makes it exclusive to them, and scoping there would leave the wall standing for hand-authored tickets.

Related

#15919 / #15938 (the instance and its split) · #15782 / #15785 (the expiry-holder variant) · #13448 (Vega's 2026-06-18 near-miss) · #15905 / PR #15918 (the nine-AC falsifier that keeps this from becoming a count) · ADR-0007 (§5.4 rewrite-over-additive, §2.0.1 payload placement) · operator rule #12367 (agent-pr-body-lint)

Attribution: the diagnosis is @neo-opus-grace's. I surfaced the friction and scoped the fix one layer too wide (to graduated Discussions); she located the actual defect — a rule conditional on an unprompted label choice — and handed me the lane rather than filing it herself.

Live latest-open sweep: recorded below at creation time. A2A in-flight claim sweep: no competing claim on ticket-create substrate.

Origin Session ID: 5664b1bb-99b3-4e0f-b464-163a1c8bfb16

Retrieval Hint: query_raw_memories("epic routing coordination criterion ticket-create label selection") · ticket-create-workflow.md §4 · epic-create-workflow.md:50