Issue #960: [L2] Issue clustering and category detection — recognize when individual issues are symptoms of a systemic gap requiring meta-level prevention
- Open PRs matching "cluster category detect": none found.
- Closed issues matching "cluster category detect": #463 (Workflow task expectations, closed 2026-03-22) and #504 (Batch auto-dent harness, closed 2026-03-23) — neither overlaps with clustering.
- No active work in flight on this feature area.
git log --grep="cluster"surfaced commitd108c55: "fix: hook observability cluster — null-input trace, Bash gate message, import cleanup (#976)". The word "cluster" here means a grouping of related hook fixes in one PR, not issue-clustering detection. No prior work on the feature #960 proposes.git log --grep="category"surfaced multiple category-adjacent commits but none related to issue-clustering detection.- Closed issues search for "category cluster pattern": three results — #948, #959 (both recently closed meta-issues about hypothesis validation and skill-chain verification discipline), and #504 (batch harness). None address issue-clustering detection.
- Conclusion: #960 is greenfield. No prior attempt to build this, no collision risk.
- #957: "Planning phase doesn't survey existing tools." Directly related — #960 is partly a meta-detector for the class of failure #957 addresses (agents rolling their own storage instead of reusing tools).
- #940: "Auto-Dent Intelligence Debt" — cross-batch learning for auto-dent. Related in that batch-level issue grouping (Level 3 in #960's spec) depends on cross-batch data that #940 addresses. However #940 is about structured batch data persistence, not about cluster detection per se.
kaizen-gaps/SKILL.mdPhase 1.5 (kaizen #207, closed): already has clustering step — agents write root-cause hypotheses per open issue, group them, name clusters, and output a table. This is MANUAL clustering by the gaps agent on demand. It works but is not triggered at filing time, not triggered at evaluate time, and not automated.kaizen-reflect/SKILL.mdSteps 2b and 2c: already has category-naming before dispositioning (group impediments by shared root cause, then ask "new pattern or recurrence?"). This fires per-reflection. However: the scope is within a single reflection session — it only sees the current session's impediments, not historical issues across sessions.kaizen-file-issue/SKILL.mdStep 2 (Search for duplicates): already doesgh issue list --search "<keywords>"for open and closed issues before filing. This is exact-issue deduplication, NOT cluster detection. It prevents filing a second report for the same bug. It does NOT detect "you're filing the 4th symptom of the same systemic gap."kaizen-evaluate/SKILL.mdPhase 0 (Collision detection): checks whether THIS specific issue is already being worked. No check for "has this class of problem recurred before?"src/issue-backend.ts: pluggable issue backend (GitHub CLI wrapper for list/create/edit/comment). Could support a clustering query but has no clustering logic today.src/structured-data.ts+src/section-editor.ts: storage primitives for plan/review/metadata attachments on issues and PRs. Plan would usewrite-attachmentto store cluster signals if needed.src/analysis/pr-pattern-checks.ts: detects multi-PR fix cycles (FM2) for PR-level clustering. This is structural pattern detection at the PR level. The same pattern applied to issues would serve #960's goals but it doesn't exist at the issue level today.explore-gaps.md(prompt): tells the agent to look for "clusters that suggest an unnamed problem dimension" in the oldest 20 issues — but this is an LLM instruction, not a structured step, and produces no structured output.reflect-batch.md(prompt): producesREFLECTION_INSIGHT:markers but no clustering step or meta-issue rate metric.- No
cli-experience.ts, nofailure-signatures/directory — the Tier 2 FSI from the grand synthesis has not been built.
- At-filing-time cluster signal:
kaizen-file-issuehas a dedup search but not a cluster-severity threshold ("2+ related issues → surface cluster signal"). - At-evaluation-time recurrence escalation:
kaizen-evaluatecollision detection checks for THIS issue being fixed, not for THIS CLASS of problem having recurred. - Auto-dent clustering step:
explore-gaps.mdandreflect-batch.mdhave no structured clustering step before filing; no meta-issue rate metric. - Category-level test question: no prompt or step in any skill asks "what single test would prevent this whole class from recurring?"
GOAL: When a cluster of individual issues reveals a systemic gap, the kaizen system surfaces that signal automatically — at the moment a new symptom is filed, at the moment an issue is evaluated, and during auto-dent batch runs — so that agents and the admin see the cluster signal and can respond with a meta-level prevention rather than another individual fix.
DONE WHEN:
- Running
/kaizen-file-issueon a symptom that matches 2+ existing issues (open or closed) produces a visible cluster signal before the issue is filed, and the agent names the category and asks whether to file a meta-issue instead of a symptom. - Running
/kaizen-evaluateon an issue that matches a prior closed issue by domain/label produces a visible recurrence escalation flag ("this class of problem has recurred — consider L2 enforcement rather than L1 fix"). - Running
claude -pwith theexplore-gaps.mdprompt on a repo with 5 thematically similar open issues produces at most 2 filed issues (1 meta-issue + at most 1 individual if it doesn't cluster), not 5 individual issues. - Running
claude -pwith thereflect-batch.mdprompt on a completed batch produces ameta_issue_ratemetric (fraction of filed issues that were collapsed into meta-issues) in the structured insight markers. - An E2E test using
kaizen-test-fixturewith 5 pre-seeded thematically related issues verifies outcome #3 without requiring real-world API calls to produce unpredictable results.
An external observer verifies this by: running the three skill invocations on pre-seeded fixture data and reading the output. No implementation knowledge required.
kaizen-file-issue/SKILL.mdStep 2: gh issue search before filing — deduplication exists, cluster-threshold detection does not. Plan will EXTEND Step 2 with threshold logic, not replace it.kaizen-reflect/SKILL.mdSteps 2b/2c: category-grouping within a session — partial overlap with Level 1. Plan will add a cross-session recurrence check that queries historical issues, not just current session impediments.kaizen-gaps/SKILL.mdPhase 1.5: manual clustering step, already in use. Proves the mental model is understood. Plan will make the same step available at filing time and in auto-dent prompts, not just in the gaps skill.kaizen-evaluate/SKILL.mdPhase 0: exact-issue collision detection exists. Plan will add recurrence-class detection as a new Phase 0.5 step.src/issue-backend.ts:listIssues({ search, state, labels })— can be called with keyword search. Plan will use this as the backend for the cluster query, not a new gh call pattern.src/analysis/pr-pattern-checks.ts: FM2 PR-cluster detection. Plan will mirror this pattern for issues (detect by label/keyword frequency) rather than building from scratch.src/section-editor.tswrite-attachment: for storing cluster metadata on the meta-issue. Plan will use this rather than new storage.explore-gaps.md,reflect-batch.md: prompt files with no structured clustering step. Plan will add clustering step directly to these prompts.cli-experience.ts(Tier 2 FSI from grand synthesis): does NOT exist. Plan will NOT depend on it.- No existing semantic/embedding similarity infrastructure in the codebase. Plan must not assume LLM-call infrastructure beyond what
ghprovides.
OPTION A1: Manual — /kaizen-gaps already has Phase 1.5 for this. No new code.
REJECTED because: The gap is at filing time and evaluation time, not during gap analysis. Manual is only triggered when the admin runs /kaizen-gaps. It does not surface the signal at the moment a new symptom arrives. Failure mode: the cluster is only named after 5+ symptoms have accumulated and a human decides to run a gap analysis — exactly the failure #960 describes.
OPTION A2: Threshold-based (keyword + label + time window) — SELECTED
Why it works: gh issue list --search "<keywords>" --label "<area>" --state all is a 1-second CLI call. With a threshold of 2+ matches, this fires at filing time and evaluation time without requiring LLM infrastructure. It mirrors the existing dedup search in kaizen-file-issue Step 2 — the implementor extends a pattern that already exists and is understood.
Failure mode if wrong: keyword search has both false positives (unrelated issues with the same term) and false negatives (related issues with different vocabulary). This is acceptable because the output is advisory ("these look related") not blocking. The agent uses judgment on the cluster signal.
OPTION A3: Semantic similarity (LLM-based grouping of issue bodies) REJECTED because: Requires calling an LLM inside a hook or skill step, adding latency (5-15 seconds per call), cost, and flakiness to a step that currently costs 0. Failure mode: a filing step that calls an LLM can time out, produce inconsistent results, or fail in batch contexts where cost is already tracked.
OPTION A4: Taxonomy-driven (issues audit flags category gaps)
REJECTED because: This is what /kaizen-audit-issues already does, and it is batch/manual. It doesn't solve the "signal at filing time" problem. Failure mode: the category gap is only named during periodic audits, not when the 3rd symptom arrives.
OPTION B1: Post-merge stop gate (after every PR merge) REJECTED because: Clustering is a forward-looking signal for filing and evaluation, not a backward-looking gate after code ships. Running it after every PR merge adds overhead to the critical path and fires too late — the symptom is already filed and fixed. Failure mode: adds noise to every merge while providing no value at the moment of filing.
OPTION B2: Skill step additions — SELECTED
Add the cluster check as Step 2b in kaizen-file-issue, as Phase 0.5 in kaizen-evaluate, and as a clustering step in explore-gaps.md and reflect-batch.md. Each check is exactly one gh issue list call (already done in the dedup path) with a threshold evaluation added.
Failure mode if wrong: as L1 instructions in SKILL.md files, these steps can be skipped under time pressure (per the grand synthesis's identified risk). This is mitigated by making the steps low-cost (one CLI call, one threshold check) so there's no incentive to skip, and by making the output useful (cluster signal saves time by collapsing 5 issues into 1).
OPTION B3: Scheduled/batch (auto-dent pre-pass)
PARTIAL ACCEPT: The Level 3 clustering in explore-gaps.md is batch-triggered by definition. This is included in the plan as a prompt addition, not as a new trigger mechanism.
OPTION B4: New issue creation trigger (webhook/hook) REJECTED because: No webhook infrastructure exists in kaizen for GitHub issue events. Building a webhook listener to trigger clustering on new issue creation is a new infrastructure component with deployment, reliability, and security implications. Failure mode: significant build scope for a problem that can be solved in SKILL.md with L1 instrucitons, per the grand synthesis Tier 1 model.
OPTION C1: Admin notification only (surfaced as text in the skill output) PARTIAL — but insufficient on its own because the signal exists in one session and is invisible to future agents.
OPTION C2: Auto-file a meta-issue linking the clustered issues — SELECTED (for clusters ≥3 in auto-dent)
Why it works: The meta-issue becomes a durable artifact. Future agents running /kaizen-evaluate on any constituent issue will find the meta-issue in their collision detection search. The meta-issue format is already defined in /kaizen-deep-dive.
For filing-time clusters (size 2+): the agent is prompted to decide: file symptom + link to cluster, or file meta-issue instead. The agent makes the call based on the specific context — the system surfaces the signal and asks, but does not auto-file.
Failure mode if wrong: auto-filing a meta-issue for every 3-issue cluster will create noise if keyword search returns false positives. Mitigated by: the threshold is advisory and the agent confirms before filing.
OPTION C3: Add type:cluster-symptom label to constituent issues
REJECTED because: labeling requires editing N existing issues, which is API-intensive and creates a mess of partially-labeled issues if the session dies mid-way. Failure mode: partial label application leaves the label system inconsistent, and the label itself does not name the category — it just marks symptoms without telling anyone what the category is.
OPTION C4: Structured report stored on the epic issue
PARTIAL ACCEPT: When a meta-issue is filed (for large clusters), the structured clustering data (constituent issues, root-cause hypothesis, category-level test idea) should be stored as an attachment using write-attachment, not free-form text. This is included in the implementation plan.
This is the highest-value question in the issue (#960 Level 4). The issue says: "What single meta-level test would prevent this entire category from recurring?"
OPTION D1: Ask the question in the meta-issue template — SELECTED The meta-issue body template (used when a cluster is filed) includes a section: "## Category Prevention Test — What single test would prevent this whole class?" The agent fills this in when filing the meta-issue, using the cluster's root cause as input. This is L1 — it requires the filing agent to reason about it, but the section header makes the question explicit and unavoidable. Failure mode if wrong: agents write "add tests" or "improve coverage" rather than naming a specific test invariant. Mitigated by: the section template provides an example ("all .sh files >50 lines must have a .bats file") and requires a machine-checkable formulation, not a prose description.
OPTION D2: Trigger /kaizen-deep-dive automatically when cluster is detected
REJECTED because: /kaizen-deep-dive runs a full research + meta-issue + implementation cycle. Triggering it automatically on every cluster detection would be too expensive and presumes the cluster warrants a deep dive. Failure mode: every 3-issue cluster launches a deep-dive subagent that discovers it's not actually a systemic gap — wasted tokens and confusion about what's happening.
HYPOTHESIS: The core assumption of issue #960 is that surfacing cluster signals at the right moments (filing, evaluation, batch runs) will cause agents to respond with meta-level prevention (a category-level test, a meta-issue, an L2 escalation) rather than another individual fix — and that this will reduce the rate of the same class of problem recurring.
VALIDATION: The cheapest available validation is to check whether the existing partial implementations (kaizen-reflect Steps 2b/2c, kaizen-gaps Phase 1.5) are actually being used and producing category issues. If agents already have the cluster-naming instruction in kaizen-reflect and still file individual symptom issues (as the issue documents with Cluster A's 5 hook-test issues filed months apart), the problem is not "the clustering step doesn't exist" but "the step is buried / not triggered at the right moment."
Run this validation before full implementation:
- Check the last 10 closed issues in kaizen — how many were filed as individual symptoms for a class that had a prior closed issue?
- Check the last 5 reflect PRs — did Steps 2b/2c fire and produce category issues?
This takes 5 minutes with gh issue list and gh pr list.
IF WRONG: If the kaizen-reflect Steps 2b/2c are already firing correctly and still failing to prevent cluster accumulation, then the hypothesis that "earlier detection = less recurrence" needs refinement. The failure mode might instead be: clusters are detected, meta-issues are filed, but the category-level test (Level 4) is never written. In that case the plan should prioritize Level 4 (the category-level test question) over Levels 1-3 (the detection machinery).
IMPORTANT NOTE: The hypothesis is probably correct but weakly so. The grand synthesis (r5-grand-synthesis.md) says the real problem is L1 steps being skipped under pressure over time. The cluster detection in kaizen-reflect already exists as L1 and is clearly being bypassed. This means the plan must be realistic: Level 1 and Level 2 of the issue's proposed fix are L1 skill-text additions that will work initially and degrade over time. They need either a test harness to verify they're being followed, or eventual escalation to L2 hooks. The plan should include this observation explicitly and file the L2 escalation path as a follow-up.
All tasks trace back to DONE WHEN criteria.
File: .claude/skills/kaizen-file-issue/SKILL.md
What: Extend the existing duplicate search (Step 2) with a threshold evaluation. After the existing gh issue list --search "<keywords>" call, count matches. If 2+ matches found: surface cluster signal. Present the matches, name the category hypothesis, and ask: "These look related — is this a symptom of a category? Consider filing a meta-issue instead."
Traces to: DONE WHEN #1 — "produces a visible cluster signal before the issue is filed."
Key detail: The cluster signal is advisory. The agent decides whether to file the symptom or the meta-issue. The skill text must make clear: filing the symptom is acceptable (add a cross-reference), filing the meta-issue is better (use the meta-issue template from Task 3).
File: .claude/skills/kaizen-evaluate/SKILL.md
What: Insert Phase 0.5 between Phase 0 (exact-issue collision detection) and Phase 0.5 (existing spec check). The new step: after checking if THIS issue is already being worked, check whether THIS CLASS of issue has recurred. Query: gh issue list --repo "$ISSUES_REPO" --state closed --search "<keywords from issue title>" --label "<area_label>" --limit 5. If 1+ match found: emit recurrence escalation flag: "This class of problem has occurred before (see #N). Consider escalating to L2 enforcement rather than another L1 fix."
Traces to: DONE WHEN #2 — "produces a visible recurrence escalation flag."
Key detail: The recurrence check uses the issue's area label as a filter to reduce false positives. A "hook test coverage" issue should query closed issues with area/testing. The instruction must be specific enough that the agent can construct the right query.
File: .claude/skills/kaizen-file-issue/SKILL.md (new section) OR .claude/kaizen/policies.md (if it belongs to general filing policy)
What: Add a meta-issue body template that agents use when a cluster is confirmed. Template sections: "## The Category", "## Constituent Issues (Symptoms)", "## Root Cause", "## Category Prevention Test — What single test would prevent this whole class? [example: all .sh files >50 lines must have a .bats file — checkable by a CI lint]", "## Compound Fix Strategy".
Traces to: DONE WHEN items 1 and 3 (meta-issue output has the Category Prevention Test section) and #960's Level 4 success criterion.
Key detail: The template is in the skill text, not in a separate file, so it's always available without an extra read.
File: prompts/explore-gaps.md
What: Before the "Output" section, add: "Clustering step — before filing any issues: (1) List all candidate issues you plan to file. (2) Group any that share a root cause. (3) For groups of 3+, file 1 meta-issue (using the template from kaizen-file-issue) instead of N individual issues. (4) For groups of 2, file the symptom with a cross-reference to the related issue." Also add: "Output metric: report how many issues were collapsed into meta-issues (meta-issue rate = meta_issues / (meta_issues + individual_issues))."
Traces to: DONE WHEN #3 — "produces at most 2 filed issues" from 5 thematically similar ones.
Key detail: The prompt currently says "look for clusters that suggest an unnamed problem dimension" but gives no structured step for handling them before filing. This task makes the step concrete.
File: prompts/reflect-batch.md
What: Add a "Cluster analysis" section to the structured reflection task. The agent must: (1) Group the issues filed in this batch by shared root cause. (2) Calculate meta-issue rate. (3) Emit: REFLECTION_INSIGHT: meta_issue_rate=<N>/<M> as a structured marker. Also: if meta_issue_rate < 20% and batch filed 5+ issues, emit REFLECTION_INSIGHT: Clustering step may have been skipped — review filed issues for category opportunities.
Traces to: DONE WHEN #4 — "produces a meta_issue_rate metric in the structured insight markers."
Key detail: The REFLECTION_INSIGHT: format already exists and is consumed by the auto-dent harness. The new marker uses the same format so no harness changes are required.
File: src/e2e/cluster-detection.test.ts (new file)
What: Using the kaizen-test-fixture repo, pre-seed 5 issues with related titles (hook test coverage theme). Run claude -p with explore-gaps.md prompt against the fixture. Assert: total filed issues ≤ 2 (at most 1 meta-issue + 1 individual), and at least 1 filed issue references the others as constituent issues.
Traces to: DONE WHEN #5 — E2E test verifies outcome #3.
Key detail: This test is necessarily an LLM-calling E2E test (per CLAUDE.md: "SKILL.md / prompt changes — the only real test is claude -p with the skill invoked"). It will be slow (~30-60 seconds) and should run in the E2E suite, not in unit tests. Mock the gh calls or use the fixture repo.
What: Per the Scope Reduction Discipline gate: Tasks 1-5 are L1 additions. The grand synthesis says L1 additions work initially and degrade under pressure. The L2 escalation path is: a stop-gate hook that reads the REFLECTION_INSIGHT: meta_issue_rate=... marker from the latest reflection and warns when meta_issue_rate drops below a threshold for 3+ consecutive batches. File this as a kaizen issue before the PR ships. The filed issue is the mechanism that makes "escalate to L2 later" not a promise without enforcement.
Traces to: Scope Reduction Discipline gate — this is required to permit the L1-only scope of Tasks 1-5.
BEHAVIOR: Cluster threshold check at filing time
LIVES IN: .claude/skills/kaizen-file-issue/SKILL.md — Step 2b (new sub-step)
TESTED IN: The behavior is LLM-driven skill text; unit-testable part is the threshold decision (2+ matches → surface signal). An extraction seam would be a helper function evaluateClusterSignal(matches: Issue[]): ClusterSignal | null in a new src/cluster-detection.ts module.
TEST APPROACH: Unit test for evaluateClusterSignal (pure function, threshold logic). E2E test for the full skill invocation behavior.
SEAM: evaluateClusterSignal(matches) — injected with a static issue list, no GitHub calls in the unit test.
BEHAVIOR: Recurrence escalation at evaluation time
LIVES IN: .claude/skills/kaizen-evaluate/SKILL.md — new Phase 0.5
TESTED IN: E2E only for the full skill invocation; the query logic is gh CLI. If a helper function buildRecurrenceQuery(issue: {title, labels}) is extracted to src/cluster-detection.ts, it can be unit tested.
TEST APPROACH: Unit test buildRecurrenceQuery — given an issue title and area label, produces the correct gh search query string. E2E test for the full evaluation behavior against fixture.
SEAM: buildRecurrenceQuery(issue) — pure function, testable without gh calls.
BEHAVIOR: Clustering step in explore-gaps prompt
LIVES IN: prompts/explore-gaps.md
TESTED IN: src/e2e/cluster-detection.test.ts
TEST APPROACH: E2E only — this is LLM-driven behavior in a prompt file. Per CLAUDE.md: cannot be unit tested.
SEAM: Pre-seeded fixture issues in Garsson-io/kaizen-test-fixture. The test runs claude -p with the prompt and asserts on filed-issue count and structure.
BEHAVIOR: meta_issue_rate metric emission
LIVES IN: prompts/reflect-batch.md
TESTED IN: Unit test for the structured marker parsing (if the harness parses meta_issue_rate=N/M), E2E test for the full reflection behavior.
TEST APPROACH: Unit test for parsing logic. E2E for the emission behavior.
SEAM: The REFLECTION_INSIGHT: parser in the auto-dent harness already exists. If meta_issue_rate=N/M is parsed, the seam is the parser function. If the harness doesn't parse this format, parsing must be added — but the marker emission is still testable via regex on the raw output.
BEHAVIOR: Category Prevention Test section in meta-issue body
LIVES IN: .claude/skills/kaizen-file-issue/SKILL.md — meta-issue template
TESTED IN: E2E only — LLM-driven content generation.
TEST APPROACH: E2E — verify that a filed meta-issue body contains a ## Category Prevention Test section with non-empty, machine-checkable content.
SEAM: The filed issue body is readable via gh issue view — assertions on the section content are possible without reading the LLM internals.
RED FLAGS CHECKED:
kaizen-file-issue/SKILL.mdis a prompt file, not TypeScript — no "5 imports" red flag applies.- Tasks 1-5 are prompt/skill additions. The only TypeScript is Task 6 (E2E test) and the optional helper functions in
src/cluster-detection.ts. src/e2e/cluster-detection.test.tsfollows the existing E2E pattern insrc/e2e/(seesetup-live.test.ts,issue-routing.test.ts).- No behavior is placed in a CLI entry point's
main().
-
Threshold-based keyword clustering over semantic similarity — the gh search
--searchAPI provides keyword matching at zero LLM cost. False positives are acceptable because the output is advisory, not blocking. -
Skill-text additions (L1) with a filed follow-up for L2 escalation — per the grand synthesis, L1 additions are the minimum viable change. They require no infrastructure, work immediately, and are faster to ship. The L2 escalation (stop-gate on meta_issue_rate) is filed as a kaizen issue, not deferred informally.
-
Meta-issue template embedded in skill text — not in a separate file. Reduces the number of places agents need to look, and ensures the template is always read as part of the filing context.
-
Category Prevention Test as a required meta-issue section — the question "what single test prevents this whole class?" is the highest-value output of #960. It must be in the meta-issue template, not left as an optional question.
-
E2E test against kaizen-test-fixture — required because skill/prompt changes cannot be verified by unit tests alone (CLAUDE.md: behavioral tests for LLM-driven skills require
claude -pinvocations).
- LLM-based semantic similarity (Design Question A, Option A3): adds latency and cost to a step that should be fast. Rejected in favor of keyword search.
- Post-merge stop gate trigger (Design Question B, Option B1): fires too late and too often. The right moment for cluster detection is before filing, not after merging.
- Auto-labeling constituent issues with
type:cluster-symptom(Design Question C, Option C3): API-intensive, partial failure leaves inconsistent labels. Rejected in favor of a meta-issue that links the symptoms. - Auto-triggering
/kaizen-deep-diveon cluster detection (Design Question C, Option D2): too expensive; the cluster signal should prompt a human/agent decision, not automatically launch a full deep-dive cycle.
The hypothesis is correct but fragile. The kaizen-reflect Steps 2b/2c already contain category-naming logic, and yet Cluster A (5 hook-test issues filed months apart) still accumulated. This is evidence that L1 skill-text additions degrade under pressure. The L1 plan is the right first step (per grand synthesis Tier 1 sequencing), but an implementor should not assume it will permanently solve the recurrence problem. The L2 escalation follow-up (Task 7) is not optional — it is the mechanism that makes "escalate later" credible.
The issue's Level 4 output (the category-level test question) is probably the highest-leverage piece — not the detection machinery (Levels 1-3). An agent who files 5 issues and never asks "what single test would prevent the whole class?" has failed at the meta-level even if the individual issues are well-formed. The meta-issue template's Category Prevention Test section is the implementation of Level 4, and it must be non-optional.