Issue 1557 — Active-task canonical entity labels
Problem
The facts-backed active-task ledger stores one category=project fact per entity/key. Supersession previously matched only the exact entity string, so case and naming variants such as humanizer / Humanizer or hybrid-memory / Hybrid-Memory PR Queue were treated as separate tasks. When a task was closed under one variant, older active status/next facts under another variant stayed current and kept appearing in the active-task projection.
Implemented fix
This is the implementation PR for the runtime active-task canonicalisation fix, not a docs-only scaffold.
- Added
canonicalLabel(entity)for active-task entities:- trims and lower-cases labels
- strips trailing
PR Queue,pull request queue,pr-stewardship, andpull request stewardshipsuffixes - collapses whitespace, underscores, and dashes to a single dash separator
- Added case-insensitive / separator-tolerant terminal status normalisation via
normalizeTaskStatus. - Updated project fact grouping to group active-task rows by canonical label, while preserving a human-facing display label from the newest variant.
- Updated
upsertProjectTaskKeyto find and supersede previous facts by canonical label + key instead of exact entity + key. - Updated terminal status writes (
done,completed,cancelled,closed,abandoned,superseded, case-insensitive) to supersede all current facts for the canonical task label, not only exact entity matches. - Stored canonical label metadata in
provenanceJsonfor new active-task facts as{ activeTask: { canonicalLabel }, canonical_label }so future grouping has an explicit canonical key while legacy rows still fall back to computed canonical labels. - Added
active-tasks backfill-canonical-labelsCLI command for facts ledgers. It backfills canonical label metadata for existing project rows, collapses duplicate key facts by canonical label, and if a terminal status exists, supersedes all other current facts in that canonical group.
Regression coverage
Added tests in extensions/memory-hybrid/tests/task-ledger-facts.test.ts for:
canonicalLabelcollapsing case, separators, and active-task suffix variants.- case-insensitive terminal status mapping (
Done,COMPLETED, etc.). groupProjectFactsByEntitycollapsinghumanizer/Humanizerrows.- upserting
HumanizerasDONEsuppressing legacyhumanizeractive rows fromloadTaskLedgerFromFactsactive projection. backfillActiveTaskCanonicalLabelscollapsing legacyhybrid-memory,Hybrid-Memory, andHybrid-memory PR Queuevariants.- backfill preserving existing provenance metadata and choosing non-superseded keepers when collapsing duplicates.
Verification run
From extensions/memory-hybrid:
npm test -- --run tests/task-ledger-facts.test.ts
# 1 passed, 15 tests passed
npm test -- --run tests/stage-active-task.test.ts tests/active-task-checkpoint.test.ts
# 2 passed, 19 tests passed
npm run format:check -- services/task-ledger-facts.ts cli/active-tasks.ts tests/task-ledger-facts.test.ts
# passed
npm run build
# passed
npm run lint -- services/task-ledger-facts.ts cli/active-tasks.ts tests/task-ledger-facts.test.ts
# exited 0; repository-wide warnings remain pre-existing
Operator note
After merging/deploying, run the maintenance command against the live facts ledger:
openclaw hybrid-mem active-tasks backfill-canonical-labels --dry-run
openclaw hybrid-mem active-tasks backfill-canonical-labels
This command refreshes the rendered ACTIVE-TASKS.md projection after applying changes.