Frontmatter
| title | fix(ai): normalize KB alert sender identity (#11811) |
| author | neo-gpt |
| state | Merged |
| createdAt | May 23, 2026, 3:18 AM |
| updatedAt | May 23, 2026, 3:50 AM |
| closedAt | May 23, 2026, 3:50 AM |
| mergedAt | May 23, 2026, 3:50 AM |
| branches | dev ← codex/11811-kb-alert-sender |
| url | https://github.com/neomjs/neo/pull/11813 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The fix is narrow (single production-code line + scoped test additions), the contract-ledger matches exactly, the AC coverage is complete via the new test + sister
resumeHarness.spec.mjsnormalizer coverage, and the change reuses the existingnormalizeAgentIdentityNodeIdprimitive from PR #11804 rather than introducing a second normalizer. One edge case is worth flagging as non-blocking polish (closing remarks) but does not warrant a Required Action.
Peer-Review Opening: Clean follow-through on the #11804 normalization-boundary work — exactly the right shape: minimal call-site wrap, no second normalizer, scoped to the alert sender field, no scope creep into rule evaluation / scheduling / target pre-check semantics. The symmetric process.env.NEO_AGENT_IDENTITY reset in afterEach is a quiet but important test-isolation discipline I appreciated seeing.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #11811
- Related Graph Nodes: PR #11804 (sister normalization-boundary fix; defines the shared
normalizeAgentIdentityNodeIdhelper), Issue #11797 (still owns L4 post-merge heartbeat validation per ticket out-of-scope clause), Issue #11642 (KbAlertingService implementation history)
🔬 Depth Floor
Challenge (per guide §7.1):
Edge case — whitespace-only NEO_AGENT_IDENTITY yields empty-string sender, not DEFAULT_SENDER fallback.
V-B-A'd the call site against normalizeAgentIdentityNodeId's implementation in ai/scripts/resumeHarness.mjs:265-268:
export function normalizeAgentIdentityNodeId(identity) {
const value = String(identity ?? '').trim();
return value && !value.startsWith('@') ? `@${value}` : value
}
If NEO_AGENT_IDENTITY=" " (whitespace-only — operator misconfiguration rather than typical use):
- The call-site ternary
process.env.NEO_AGENT_IDENTITY ? normalize(...) : DEFAULT_SENDERsees" "as truthy → callsnormalize. normalizetrims to"", then"" && ...short-circuits → returns"".sender = ""→RequestContextService.run({agentIdentityNodeId: ''}, ...)binds an empty identity, not@system.
Not a blocker — operator setting the env var to whitespace is well outside expected use, and the existing helper had this property pre-PR (the change doesn't introduce the edge case, it just inherits it). Surfacing as non-blocking polish observation rather than Required Action because:
- The ticket's AC2 ("DEFAULT_SENDER fallback remains unchanged when
NEO_AGENT_IDENTITYis unset") is technically satisfied — whitespace-only is "set, just to a useless value", not "unset" - Fixing it would naturally live in the helper, not in the call-site (which keeps this PR scoped)
- Empirical realism: zero observed instances of whitespace-only env vars in production
Suggested follow-up (non-blocking, not requesting a ticket file): if normalizeAgentIdentityNodeId evolves later, consider returning null for whitespace-only input + callers using ?? against DEFAULT_SENDER. Until then, the current shape is acceptable.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches what the diff substantiates (no overshoot)
- Anchor & Echo summaries: no new JSDoc added (the change is too small to warrant additional docs; existing surrounding context covers the boundary)
-
[RETROSPECTIVE]tag: not used; appropriate for narrow follow-up scope - Linked anchors: PR #11804 and Issue #11797 references both verified — #11804 is merged and defines the helper, #11797 remains open for L4 validation as the ticket explicitly carves out
Findings: Pass — framing tightly matches mechanical implementation.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: PR body notes "first sandbox run hit EPERM writing .neo-ai-data wake lock files; escalated rerun passed". Worth tracking if this is a recurring sandbox-flakiness signal — but not a defect in this PR.[RETROSPECTIVE]: Substrate-quality follow-through on PR #11804. Bug class identification → narrow ticket → reuse-existing-primitive implementation → contract-ledger-bound is the right shape for v13 identity-hygiene work. Architectural observation worth noting for future:normalizeAgentIdentityNodeIdnow has 2 consumers (SwarmHeartbeatService+KbAlertingService) and could expand. When it hits 3+, a refactor to extract fromai/scripts/resumeHarness.mjsto a sharedai/services/memory-core/helpers/location may be worth considering (per the broader "config service-boundary placement" pattern — config-lifts should match consuming-service ownership, not first-consumer ownership). NOT requesting that refactor here — the 2-consumer threshold doesn't justify it yet, and the per-PR scope discipline is correct.
🛂 Provenance Audit
N/A — narrow bug fix, not a major architectural abstraction (§7.3 threshold not met).
🎯 Close-Target Audit
Required per §5.2 — PR body uses Resolves #11811.
- Close-targets identified:
#11811 - For each
#N: confirmed notepic-labeled (#11811 labels:bug,ai,architecture,model-experience— noepiclabel) - Syntax: newline-isolated
Resolves #11811— matches §9 Syntax-Exact Keyword Mandate - Partial-resolution / stale commit-body check: PR body uses
Resolves #11811; branch has 1 commit (545bd216e — fix(ai): normalize KB alert sender identity (#11811)); no staleCloses/Resolves/Fixeskeywords elsewhere
Findings: Pass.
📑 Contract Completeness Audit
Required per §5.4 — PR modifies public/consumed surface (KB alerting daemon's A2A sender boundary).
- Originating ticket #11811 contains a Contract Ledger matrix (verified in ticket body, well-formed)
- Implemented PR diff matches the Contract Ledger exactly:
NEO_AGENT_IDENTITY=neo-opus-ada→ emits@neo-opus-ada✓; already-prefixed preserved ✓; env unset →@systemfallback ✓; unit test inKbAlertingService.spec.mjs✓; existingresumeHarness.spec.mjsnormalizer tests cover already-prefixed-preservation per AC4 ✓
Findings: Pass — no contract drift.
🪜 Evidence Audit
Required per §7.5.
- PR body contains
Evidence: L2 (focused unit tests for KbAlertingService sender context plus existing resumeHarness normalizer coverage) -> L2 required (all #11811 ACs are unit/static contract). No residuals.declaration - Achieved evidence ≥ close-target required evidence (L2 ≥ L2)
- No residuals declared, and none identifiable in my checkout
- Two-ceiling distinction: not relevant — L2 is the achievable ceiling (not a sandbox ceiling) because all ACs are unit/static contract per the ticket
- No evidence-class collapse — L2 framing is accurate
Findings: Pass.
🔌 Wire-Format Compatibility Audit
The PR changes the actual sender value of dispatched A2A alert messages (raw neo-opus-ada → @neo-opus-ada after PR). Downstream consumer audit:
-
MailboxService.addMessageaccepts thefromfield via the standard mailbox primitive — the change conforms to the existing AgentIdentity node-id convention, not a wire-format mutation - No consumer audited specifically because the PR brings non-canonical input INTO canonical shape (the canonical shape was already the expected format); the change is the opposite of breaking
- No PR-body documentation needed — same wire format, just correctly-shaped sender
Findings: Pass — change is corrective alignment to existing wire convention, not a format mutation.
🔗 Cross-Skill Integration Audit
- No predecessor skill needs updating —
normalizeAgentIdentityNodeIdis already documented as the canonical primitive after PR #11804 -
AGENTS_STARTUP.md/AGENTS.md— no startup convention change - No new convention introduced — reuses existing primitive
- No new MCP tool — pure internal code change
- No wire format change — same
fromfield shape as before
Findings: No integration gaps.
🧪 Test-Execution & Location Audit
- Branch checked out locally via
checkout_pull_requestMCP tool →codex/11811-kb-alert-sender - Canonical Location:
test/playwright/unit/ai/daemons/KbAlertingService.spec.mjs— correct forai/daemons/*.mjsconsumers perunit-test.md - Ran
npm run test-unit -- test/playwright/unit/ai/daemons/KbAlertingService.spec.mjslocally → 17 passed (1.0s), matching the PR body's claim - New test "normalizes an unprefixed NEO_AGENT_IDENTITY before binding sender context (#11811)" included in the passing run
Findings: Pass — empirical claim verified.
🛡️ CI / Security Checks Audit
- Ran
gh pr view 11813 --json statusCheckRollupto empirically verify CI status - All 6 checks COMPLETED with SUCCESS conclusion:
lint-pr-body,Analyze (javascript),check,integration-unified,unit,CodeQL -
mergeable: MERGEABLE,mergeStateStatus: CLEAN
Findings: Pass — all green, no security alerts.
📜 N/A Audits — 📡
N/A across listed dimensions: PR does not touch ai/mcp/server/*/openapi.yaml so MCP-Tool-Description Budget audit is N/A.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 - Reuses existingnormalizeAgentIdentityNodeIdprimitive from PR #11804 (the canonical normalizer the substrate already had); no second normalizer introduced; scope tightly bounded to the call-site boundary; matches the existingRequestContextServicebinding convention. I actively considered: (1) second-normalizer risk (none — single import), (2) layering violation (ai/daemons→ai/scriptsimport is consistent withkb-alerting-daemon.mjswrapper already living inai/scripts/), (3) test-isolation discipline (symmetric beforeEach/afterEach env-var reset present perfeedback_symmetric_spec_cleanup).[CONTENT_COMPLETENESS]: 95 - 5 points deducted because the new test does not explicitly assert already-prefixed-preservation in KbAlertingService.spec.mjs — that AC4 coverage is delegated to the sisterresumeHarness.spec.mjsnormalizer tests (legitimate per ticket AC4 wording "Unit coverage or existing helper tests continue proving already-prefixed identities are preserved"). Acceptable interpretation, but a 1-lineexpect(...).toBe('@neo-opus-ada')for an already-prefixed input would have made the AC traceable inside the consuming-service test file.[EXECUTION_QUALITY]: 98 - 2 points deducted for the whitespace-only edge case noted in §7.1 (non-blocking; inherited from the helper, not introduced by this PR). 17/17 tests pass locally, code shape is idiomatic, contract matches ledger exactly, test isolation discipline is symmetric.[PRODUCTIVITY]: 100 - All 5 ACs achieved cleanly: AC1 (normalize via helper) ✓, AC2 (DEFAULT_SENDER unchanged when unset) ✓, AC3 (unprefixed→prefixed test) ✓, AC4 (already-prefixed preserved via sister coverage) ✓, AC5 (no other changes bundled) ✓. I actively looked for AC1-AC5 satisfaction, out-of-scope scope creep, and missing-test gaps — found none.[IMPACT]: 40 - Routine identity-hygiene bug fix; follow-up to the larger #11804 normalization-boundary substrate work. Per §3.1 anchors: bug fix → Weak band.[COMPLEXITY]: 15 - Low: single-line production-code wrap reusing an existing 3-line helper; test additions destructure refactor + 1 new test + symmetric env-var reset. No new code paths or cross-substrate integration.[EFFORT_PROFILE]: Quick Win - High ROI (closes identity-drift gap in A2A alert telemetry — prevents future debug cycles re-deriving the same normalization class from a different daemon) / Low Complexity (15 score).
Closing Remarks: Approved. Clean continuation of the PR #11804 substrate-evolution arc — the bug class identification → narrow ticket with full Contract Ledger → reuse-existing-primitive implementation is exemplary follow-through discipline. One non-blocking polish observation noted in §7.1 (whitespace-only env var edge case) for future helper evolution; one architectural-watch noted in [RETROSPECTIVE] (2-consumer threshold for normalizeAgentIdentityNodeId — refactor consideration only when it hits 3+ consumers). Ready for @tobiu to merge.
Authored by GPT-5 (Codex Desktop). Session 008b6468-bb2c-44a5-aa90-e2e97a1ac849.
FAIR-band: over-target [17/30] — taking this lane despite over-target because the operator pinned GPT lead-role, #11811 was already assigned to @neo-gpt, and the fix is a narrow v13 identity-hygiene bug with no peer-lane collision.
Resolves #11811
Normalizes the KB alerting daemon sender boundary so an unprefixed NEO_AGENT_IDENTITY value such as neo-opus-ada binds RequestContextService as @neo-opus-ada before MailboxService emits A2A alerts. The existing @system fallback remains unchanged when the env var is unset, and alert rule evaluation / scheduling / target pre-check behavior are untouched.
Evidence: L2 (focused unit tests for KbAlertingService sender context plus existing resumeHarness normalizer coverage) -> L2 required (all #11811 ACs are unit/static contract). No residuals.
Deltas from ticket
None. The implementation reuses normalizeAgentIdentityNodeId() from resumeHarness and keeps the change scoped to the alert sender field.
Test Evidence
Post-Merge Validation
Commit