Frontmatter
| id | 10085 |
| title | [enhancement] ConceptIngestor follow-ups post-#10035 — hoist concept-graph pass to cycle-scope + dedicated spec + observability polish |
| state | Closed |
| labels | enhancementaiarchitecture |
| assignees | tobiu |
| createdAt | Apr 19, 2026, 12:31 PM |
| updatedAt | Apr 19, 2026, 6:19 PM |
| githubUrl | https://github.com/neomjs/neo/issues/10085 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | 10030 |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Apr 19, 2026, 4:45 PM |
[enhancement] ConceptIngestor follow-ups post-#10035 — hoist concept-graph pass to cycle-scope + dedicated spec + observability polish

Input from Claude Opus 4.7 (Claude Code):
❋ ## Scope Reduction — PR #10100 closes Items 1 + 2 only
Ticket-intake review (per
.agent/skills/ticket-intake/references/ticket-intake-workflow.md§1.4, Hypothesis-vs-Root-Cause Validation) found that Items 3 and 4 fail the prescription-level gate: both prescribe logger-based fixes for a daemon where logger output is ephemeral and durable observability lives on graph-nodecapabilityGap+sandman_handoff.mdrendering.Items landed in #10100
- Item 1 — Hoisted
inferConceptGraphGapsfrom per-session to cycle-scope inDreamService.processUndigestedSessions. Dropped theexecuteCapabilityGapInferencewrapper; promoted both inference entry points to public API with matching thin facades on DreamService.- Item 2 — New
test/playwright/unit/ai/daemons/services/ConceptIngestor.spec.mjswith 6 AC coverage targets plus stats-shape contract (7 tests total). Full daemon + ConceptService suite: 37/37 green across 3 runs.Items dropped (rationale)
Item 3 —Redundant.logger.infostats re-emission in DreamServiceConceptIngestor.syncConceptsToGraphalready emits a phase-level summarylogger.infoat completion (ai/daemons/services/ConceptIngestor.mjs:272). A DreamService-level re-emission adds noise without a consumer. The Item 3 premise ("the return shape exists so surface it") inverts the reasoning — presence of a return value isn't demand for a log line.
Item 4 — Consolidate per-orphanSuperseded by #10087. The correct architecture for orphan surfacing is:logger.warn
- New
[ORPHAN_CONCEPT]tag in the existingcapabilityGapchannel (same pattern as[EXAMPLE_GAP]in #10084)- Weight-gated by the same
GUIDE_GAP_WEIGHT_THRESHOLDas[GUIDE_GAP]— silent today when ingestion derives concepts from guide titles only; auto-surfaces as richer ingestion from #10036 / #10037 / #10050 populates meaningful weight signals- Rendered as
⚠️ Orphaned Conceptssection insandman_handoff.mdviaGoldenPathSynthesizer— uniform 5-item limit + TTL pruninglogger.warndeleted entirely once the capabilityGap channel covers the signalLanding Item 4 here would cement the wrong mechanism (logger-based) right before #10087 replaces it. Per-orphan warn preserved in #10100; deletion ships with #10087.
AC status after scope reduction
inferConceptGraphGapshoisted from per-session to cycle-scope in DreamServicetest/playwright/unit/ai/daemons/services/ConceptIngestor.spec.mjscreated with the six coverage targetsStats logging surfaces in DreamService REM cycle output— dropped (redundant withConceptIngestor.mjs:272)Orphan warnings consolidated into summary form— superseded by #10087 (architectural channel promotion)- Existing PR #10084 tests still pass (regression guard) — 37/37 green
On merge, closing the ticket is correct: Items 3 and 4 are addressed by reframing, not by code here. The full rationale is captured in the PR body and this comment so humans and future agents landing on #10085 see why 2 of 4 items were dropped without silent abandonment.
Origin Session ID: 1db25bbe-f39d-4dcd-bb0e-bc125ce91326
Context
Follow-up items surfaced during the self-review of PR #10084 (resolves #10035). The refactor landed cleanly (all 30 tests green), but four polish items were deferred as explicit out-of-scope flags rather than smuggled into the main PR. This ticket bundles them because all four touch the same two files (
ai/daemons/DreamService.mjs+ai/daemons/services/ConceptIngestor.mjs) and sequencing them together avoids conflicting patches.The Four Items
1. Hoist
inferConceptGraphGapscall from per-session to cycle-scope (efficiency)Problem:
GapInferenceEngine.executeCapabilityGapInference(session, payload)invokes bothinferTestGapsFromSession(session-scoped, correct) ANDinferConceptGraphGaps(ontology-scoped, wasteful in a loop) once per session. DreamService's REM loop iterates N sessions per cycle, so the concept-graph pass runs N times with the same output. Correctness is unaffected (writes are idempotent), but it's wasted work that grows with ontology + session count.Fix: Extract
inferConceptGraphGapsto a standalone method callable on GapInferenceEngine; invoke it once from DreamService.processUndigestedSessions after the per-session loop, beforerunGarbageCollection.2. Create
test/playwright/unit/ai/daemons/services/ConceptIngestor.spec.mjs(coverage)Problem: Currently tested only indirectly via DreamService integration tests. The service's contract at the unit level isn't locked in.
Coverage targets:
logger.warn)CONCEPT_EDGE_TYPESconstantsfile:→ FILE,ext:→ EXT, fallback → CONCEPT){conceptsProcessed, conceptsUpserted, conceptsSkipped, edgesReplaced, orphansDetected, errors})3. Log
syncConceptsToGraphreturned stats in DreamService (observability)Problem:
await ConceptIngestor.syncConceptsToGraph()returns a stats object that DreamService currently discards. The return shape exists for observability; dropping it on the floor means cycle-time / differential-sync benefits aren't visible in REM cycle logs.Fix: Capture the returned stats; emit via
logger.info('[DreamService] Concept ingestion: ...', stats)before the per-session loop begins.4. Consolidate per-orphan
logger.warninto summary (log hygiene)Problem:
ConceptIngestor.syncConceptsToGraphemits onelogger.warnper orphan concept. At 17 orphans today × N REM cycles per day = log spam. Grows linearly with ontology size.Fix: Accumulate orphan IDs/names into an array during the sync loop; emit a single summary warn at completion with count + top N names (e.g., first 10 by weight).
Acceptance Criteria
inferConceptGraphGapshoisted from per-session to cycle-scope in DreamServicetest/playwright/unit/ai/daemons/services/ConceptIngestor.spec.mjscreated with the six coverage targets aboveOrigin Session ID
62d6f155-e57f-4279-9b59-36c9e4ecbc5e— surfaced in PR #10084 self-review commentRelated