Context
Follow-up from PR #11804 review after the #11797 swarm-heartbeat identity normalization fix merged. #11804 correctly centralized normalizeAgentIdentityNodeId() in ai/scripts/resumeHarness.mjs and used it for the folded heartbeat lane. The review surfaced one adjacent consumer of the same environment variable class: ai/daemons/KbAlertingService.mjs reads process.env.NEO_AGENT_IDENTITY directly when dispatching A2A alerts.
Verified evidence before filing:
- PR #11804 is merged and intentionally left #11797 open for L4 post-merge validation.
rg -n "NEO_AGENT_IDENTITY|DEFAULT_SENDER|normalizeAgentIdentityNodeId" ai/daemons/KbAlertingService.mjs ai/scripts/resumeHarness.mjs test/playwright/unit/ai/daemons test/playwright/unit/ai/scripts shows KbAlertingService.mjs:318 consumes process.env.NEO_AGENT_IDENTITY || DEFAULT_SENDER raw.
- The same command shows
normalizeAgentIdentityNodeId() is exported from ai/scripts/resumeHarness.mjs and already unit-covered for neo-opus-ada -> @neo-opus-ada, prefixed identities, and whitespace trimming.
- Duplicate sweep:
ask_knowledge_base("KbAlertingService NEO_AGENT_IDENTITY normalization ticket duplicate", type="ticket") found related identity-binding tickets but no KbAlertingService duplicate; exact grep over resources/content/issues / resources/content/discussions found KbAlertingService history (#11642) and NEO_AGENT_IDENTITY history, but no equivalent open follow-up. Live gh issue list --search "KbAlertingService is:issue is:open repo:neomjs/neo" returned [].
The Problem
NEO_AGENT_IDENTITY can be configured in GitHub-login form (neo-opus-ada) while A2A identity surfaces are graph node ids (@neo-opus-ada). #11804 fixed this for SwarmHeartbeatService, but KbAlertingService still passes the raw env-var value as its alert sender.
If the env var is unprefixed, KB alert messages can be emitted from a sender value that does not match the AgentIdentity node-id shape used elsewhere. That creates avoidable identity drift in A2A alert telemetry and can force future agents to re-debug the same normalization class from a different daemon.
The Architectural Reality
Relevant surfaces:
| Surface |
Current role |
ai/daemons/KbAlertingService.mjs |
Poll-loop daemon that dispatches KB alert A2A messages; currently computes sender from `process.env.NEO_AGENT_IDENTITY |
ai/scripts/resumeHarness.mjs |
Owns the newly merged normalizeAgentIdentityNodeId() helper from #11804. |
test/playwright/unit/ai/daemons/KbAlertingService.spec.mjs |
Existing daemon test owner for KbAlertingService behavior. |
test/playwright/unit/ai/scripts/resumeHarness.spec.mjs |
Existing coverage for the normalizer edge cases. |
This is not a new identity subsystem and not a broader NEO_AGENT_IDENTITY audit. It is a single known consumer using the same normalization primitive that already shipped for #11797.
The Fix
Normalize the alert sender at the KbAlertingService boundary before calling A2A dispatch:
- Reuse
normalizeAgentIdentityNodeId() rather than adding a second normalizer.
- Preserve
DEFAULT_SENDER = '@system' fallback behavior when NEO_AGENT_IDENTITY is unset.
- Add focused KbAlertingService unit coverage proving an unprefixed
NEO_AGENT_IDENTITY produces a prefixed sender in the outgoing A2A payload.
- Keep this scoped to the alert sender field; do not alter alert rule evaluation, pulse scheduling, or target pre-check semantics.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
| KbAlertingService A2A alert sender identity |
AgentIdentity node-id convention + #11804 normalizer |
NEO_AGENT_IDENTITY=neo-opus-ada emits alerts from @neo-opus-ada; already-prefixed values stay unchanged |
If env var is unset, continue using @system |
Inline JSDoc/summary remains enough; no user-facing docs needed |
Unit test in KbAlertingService.spec.mjs + existing resumeHarness.spec.mjs normalizer tests |
Decision Record Impact
none. This is aligned with the existing AgentIdentity binding convention and the #11804 normalization boundary. No ADR amendment needed.
Acceptance Criteria
Out of Scope
- Closing #11797; that ticket still needs L4 post-merge heartbeat wake validation.
- Auditing every historical
NEO_AGENT_IDENTITY consumer.
- Refactoring
normalizeAgentIdentityNodeId() into a new utility module.
- Changing A2A permission semantics or sender validation.
Avoided Traps
| Trap |
Why rejected |
Adding a second normalizer inside KbAlertingService |
Duplicates the #11804 primitive and invites drift. |
| Folding this into #11797 closeout without a ticket |
The surface is adjacent but separate: KB alert sender identity, not folded heartbeat wake dispatch. |
| Broad env-var audit |
Useful later if more consumers surface, but not needed for this concrete bug. |
Related
- PR #11804 —
fix(ai): normalize swarm heartbeat identity (#11797).
- Issue #11797 — still owns L4 post-merge heartbeat validation.
- Issue #11642 — KbAlertingService implementation history.
Origin Session ID: d60db68f-8ff0-48a6-b168-237ca9dca2a0
Handoff Retrieval Hints:
query_raw_memories("KbAlertingService raw NEO_AGENT_IDENTITY normalizeAgentIdentityNodeId #11804")
rg -n "NEO_AGENT_IDENTITY|DEFAULT_SENDER|normalizeAgentIdentityNodeId" ai/daemons/KbAlertingService.mjs ai/scripts/resumeHarness.mjs test/playwright/unit/ai/daemons test/playwright/unit/ai/scripts
Context
Follow-up from PR #11804 review after the #11797 swarm-heartbeat identity normalization fix merged. #11804 correctly centralized
normalizeAgentIdentityNodeId()inai/scripts/resumeHarness.mjsand used it for the folded heartbeat lane. The review surfaced one adjacent consumer of the same environment variable class:ai/daemons/KbAlertingService.mjsreadsprocess.env.NEO_AGENT_IDENTITYdirectly when dispatching A2A alerts.Verified evidence before filing:
rg -n "NEO_AGENT_IDENTITY|DEFAULT_SENDER|normalizeAgentIdentityNodeId" ai/daemons/KbAlertingService.mjs ai/scripts/resumeHarness.mjs test/playwright/unit/ai/daemons test/playwright/unit/ai/scriptsshowsKbAlertingService.mjs:318consumesprocess.env.NEO_AGENT_IDENTITY || DEFAULT_SENDERraw.normalizeAgentIdentityNodeId()is exported fromai/scripts/resumeHarness.mjsand already unit-covered forneo-opus-ada->@neo-opus-ada, prefixed identities, and whitespace trimming.ask_knowledge_base("KbAlertingService NEO_AGENT_IDENTITY normalization ticket duplicate", type="ticket")found related identity-binding tickets but no KbAlertingService duplicate; exact grep overresources/content/issues/resources/content/discussionsfound KbAlertingService history (#11642) and NEO_AGENT_IDENTITY history, but no equivalent open follow-up. Livegh issue list --search "KbAlertingService is:issue is:open repo:neomjs/neo"returned[].The Problem
NEO_AGENT_IDENTITYcan be configured in GitHub-login form (neo-opus-ada) while A2A identity surfaces are graph node ids (@neo-opus-ada). #11804 fixed this forSwarmHeartbeatService, butKbAlertingServicestill passes the raw env-var value as its alert sender.If the env var is unprefixed, KB alert messages can be emitted from a sender value that does not match the AgentIdentity node-id shape used elsewhere. That creates avoidable identity drift in A2A alert telemetry and can force future agents to re-debug the same normalization class from a different daemon.
The Architectural Reality
Relevant surfaces:
ai/daemons/KbAlertingService.mjsai/scripts/resumeHarness.mjsnormalizeAgentIdentityNodeId()helper from #11804.test/playwright/unit/ai/daemons/KbAlertingService.spec.mjstest/playwright/unit/ai/scripts/resumeHarness.spec.mjsThis is not a new identity subsystem and not a broader
NEO_AGENT_IDENTITYaudit. It is a single known consumer using the same normalization primitive that already shipped for #11797.The Fix
Normalize the alert sender at the
KbAlertingServiceboundary before calling A2A dispatch:normalizeAgentIdentityNodeId()rather than adding a second normalizer.DEFAULT_SENDER = '@system'fallback behavior whenNEO_AGENT_IDENTITYis unset.NEO_AGENT_IDENTITYproduces a prefixed sender in the outgoing A2A payload.Contract Ledger Matrix
NEO_AGENT_IDENTITY=neo-opus-adaemits alerts from@neo-opus-ada; already-prefixed values stay unchanged@systemKbAlertingService.spec.mjs+ existingresumeHarness.spec.mjsnormalizer testsDecision Record Impact
none. This is aligned with the existing AgentIdentity binding convention and the #11804 normalization boundary. No ADR amendment needed.
Acceptance Criteria
KbAlertingServicenormalizesprocess.env.NEO_AGENT_IDENTITYthrough the existingnormalizeAgentIdentityNodeId()helper before using it as an A2A alert sender.DEFAULT_SENDER = '@system'fallback remains unchanged whenNEO_AGENT_IDENTITYis unset.neo-opus-adaor equivalent) produces a prefixed sender in the dispatched A2A alert payload.Out of Scope
NEO_AGENT_IDENTITYconsumer.normalizeAgentIdentityNodeId()into a new utility module.Avoided Traps
KbAlertingServiceRelated
fix(ai): normalize swarm heartbeat identity (#11797).Origin Session ID: d60db68f-8ff0-48a6-b168-237ca9dca2a0
Handoff Retrieval Hints:
query_raw_memories("KbAlertingService raw NEO_AGENT_IDENTITY normalizeAgentIdentityNodeId #11804")rg -n "NEO_AGENT_IDENTITY|DEFAULT_SENDER|normalizeAgentIdentityNodeId" ai/daemons/KbAlertingService.mjs ai/scripts/resumeHarness.mjs test/playwright/unit/ai/daemons test/playwright/unit/ai/scripts