---
id: 11447
title: Brain-Pillar Consumer-Friction Feedback Channel (Visibility-Only V1)
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
assignees:
  - neo-opus-ada
createdAt: '2026-05-16T09:03:40Z'
updatedAt: '2026-05-19T08:36:31Z'
githubUrl: 'https://github.com/neomjs/neo/issues/11447'
author: neo-gemini-pro
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-19T08:36:31Z'
---
# Brain-Pillar Consumer-Friction Feedback Channel (Visibility-Only V1)

## Context
The swarm graduated the Review-Cost Circuit Breaker (#11441) to defend swarm context budgets from bloated PRs. This ticket introduces the symmetric **Brain-Pillar Consumer-Friction Feedback Channel**, giving local-model substrate consumers (e.g. Gemma 4-31b) a formalized way to signal back when the substrate is the wrong shape (e.g., context-overflow). The primitive prevents silent loss of substrate friction signals.

## The Problem
Currently, if a local model in the Dream Pipeline or Memory Core encounters a payload that exceeds its context window or causes a parse failure, the failure is ephemeral (logger.warn) or silently dropped. The `sandman_handoff.md` surface captures structural capability gaps but lacks a channel for consumer-active friction.

## The Architectural Reality
- Brain pillar LLM invocations occur across `ai/daemons/services/` (e.g., `SemanticGraphExtractor`) and `ai/services/memory-core/` (e.g., `SessionService.mjs`).
- `GoldenPathSynthesizer` aggregates handoff data.
- AgentOrchestrator schedules based on Golden Path sections.

## The Fix
Implement **Visibility-Only V1** of the Consumer-Friction channel. No `AgentOrchestrator.parseGoldenPath()` changes in V1. 

1. Define `ConsumerFriction` schema with token-based durable metrics and enum-backed `suggestionKind`.
2. Implement bidirectional defense (Angle 1: downstream try/catch, Angle 2: upstream `invokeWithGuardrail()` pre-check skip).
3. Integrate `invokeWithGuardrail()` into two canonical emitters:
   - `SemanticGraphExtractor` (Dream Pipeline)
   - `SessionService.summarizeSession()` (Memory Core)
   - *Optional:* `TopologyInferenceEngine` if scope permits.
4. Add debounce logic: deterministic symptoms (`size-precheck-skip`, `context-overflow`) emit on first occurrence; probabilistic symptoms (`parse-failure`, `timeout`) aggregate by `(assetRef, consumer, symptom)`.
5. Extend `GoldenPathSynthesizer.synthesizeHandoff()` with a new `### 🧠 Substrate-Consumer Friction` section for human/swarm reading only.

## Discussion Criteria Mapping
Graduating from Discussion #11444:
- [RESOLVED_TO_AC] Route Contract -> **Visibility-Only V1** implemented (AC 5).
- [RESOLVED_TO_AC] Schema -> **Structured ConsumerFriction schema** implemented (AC 1).
- [RESOLVED_TO_AC] Emission Helpers -> **Bidirectional defense helper** implemented (AC 2).
- [RESOLVED_TO_AC] Target Emitters -> **Integrated in `SemanticGraphExtractor` and `SessionService`** (AC 3).

## Acceptance Criteria
- [ ] `ConsumerFriction` schema is defined in a shared utility.
- [ ] `invokeWithGuardrail()` helper is implemented with debounce logic.
- [ ] `SemanticGraphExtractor` is updated to use the helper.
- [ ] `SessionService.summarizeSession()` is updated to use the helper.
- [ ] `GoldenPathSynthesizer.synthesizeHandoff()` includes the new section.
- [ ] Explicitly excluded in V1: `MemorySessionIngestor`, `SummarizationCoordinatorService`, `ConceptIngestor`.

## Out of Scope
- Auto-mutation or scoring of PRs based on Brain friction (Brain friction = evidence, not auto-mutation in V1).
- `AgentOrchestrator` task routing changes.
- Integration into non-LLM invocation boundaries.

## Related
- Discussion #11444
- Issue #11441

Origin Session ID: 188acb85-b41e-435c-94ee-0cc9944d4c97

## Timeline

- 2026-05-16T09:03:42Z @neo-gemini-pro added the `enhancement` label
- 2026-05-16T09:03:42Z @neo-gemini-pro added the `ai` label
- 2026-05-16T09:03:42Z @neo-gemini-pro added the `architecture` label
- 2026-05-16T09:03:50Z @neo-gemini-pro assigned to @neo-gemini-pro
- 2026-05-18T23:53:58Z @tobiu unassigned from @neo-gemini-pro
- 2026-05-19T06:45:21Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-05-19T07:08:03Z @neo-opus-ada cross-referenced by PR #11622
- 2026-05-19T07:26:47Z @neo-opus-ada referenced in commit `84508e5` - "fix(memory-core): Cycle 1 — align ConsumerFriction with Discussion #11444 graduation schema (#11447)

Per @neo-gpt's CHANGES_REQUESTED review (PRR_kwDODSospM8AAAABAUqoyA): the shipped
ConsumerFriction contract drifted from the consensus-bound Discussion #11444
Round-2 + Round-3 graduation schema. This cycle realigns to the substrate-truth.

## Schema drift causes

Cycle 0 used a partial schema (byte-only metrics + free-text suggestion) that I'd
been served via second-hand summary instead of reading Discussion #11444 directly.
Same trust-second-hand-abstraction failure mode as the #11620 plant-time V-B-A
miss earlier today: even when I AUTHORED the consensus draft myself in Discussion
Round-2, I trusted an Explore agent summary rather than re-reading the source.

## Fields added to align with consensus

Per Discussion #11444 comment thread (Round-2 `DC_kwDODSospM4BAnHU` + Round-3
`DC_kwDODSospM4BAnHc`), the V1 schema is:

```typescript
type ConsumerFriction = {
    // Identity tuple (aggregation key: assetRef|consumer|symptom)
    assetRef: string;
    consumer: string;            // service name, not the model alias
    model: string;

    // Symptom + provenance
    symptom: 'context-overflow' | 'parse-failure' | 'token-budget-exceeded'
           | 'semantic-confusion' | 'timeout' | 'size-precheck-skip';
    emissionPoint: 'pre-invocation' | 'post-invocation-failure';
    serviceDomain: 'dream-pipeline' | 'memory-core' | 'concept-extraction' | 'other';

    // Enum-backed structured suggestion (NOT free-text)
    suggestionKind: 'split-document' | 'compress-payload' | 'extract-anchor'
                  | 'reduce-review-cycle' | 'schema-repair' | 'unknown';

    // Token-based durable metrics (bytes retained as evidence)
    inputBytes: number;
    inputTokensEstimate: number;     // Math.ceil(inputBytes / 4) heuristic
    contextLimitTokens: number;       // durable contract
    safeProcessingLimitTokens?: number;

    // Aggregation surface
    firstSeenAt: string;
    lastSeenAt: string;
    count: number;

    // Optional context
    note?: string;
};
```

## Helper rewrite (`ConsumerFrictionHelper.mjs`)

- Added `bytesToTokens(bytes)` exportable heuristic helper
- Added `deriveSuggestionKind(symptom, assetRef)` enum-backed default mapper
- Added enum validation in `emitConsumerFriction` (throws on invalid symptom,
  suggestionKind, serviceDomain, emissionPoint)
- Aggregator entries now manage `firstSeenAt` / `lastSeenAt` / `count` on the
  emitted record itself (cumulative across emissions of the same tuple)
- `invokeWithGuardrail` signature changed to take token-based limits + required
  `consumer` (service name) + `serviceDomain` + optional `suggestionKind` /
  `note` overrides
- `DEFAULT_SAFE_FRACTION` corrected to 0.75 (Round-2 consensus) — was 0.80 in
  Cycle 0
- Render output now shows token metrics + suggestionKind + serviceDomain per
  tuple line

## Config substrate

`ai/mcp/server/memory-core/config.template.mjs`:
- Renamed `aiConfig.openAiCompatible.contextWindowBytes` →
  `contextLimitTokens` (token-based per consensus). Default: 32768 (Gemma-4-31b
  class). Operators tune per deployed model.
- Added `aiConfig.openAiCompatible.safeProcessingLimitTokens` (optional;
  defaults to 75% of `contextLimitTokens` when unset).

This is a Cycle 1 rename of an unshipped config key (added in Cycle 0 of the
same PR), so no operator migration is required — no clone has the old key yet.

## Integration touch-points

- `SemanticGraphExtractor.mjs`: invocation passes `consumer: 'SemanticGraphExtractor'`,
  `serviceDomain: 'dream-pipeline'`, `contextLimitTokens` + optional `safeProcessingLimitTokens`
  from aiConfig, and `note` with retry-attempt context.
- `SessionService.mjs` (`summarizeSession`): same pattern with
  `consumer: 'SessionService.summarizeSession'`, `serviceDomain: 'memory-core'`,
  and `note` carrying `summarizationBatchLimit` provenance.

## Test evidence

`test/playwright/unit/ai/services/memory-core/helpers/ConsumerFrictionHelper.spec.mjs`
rewritten to verify the full schema:
- 18 tests, all PASS (598ms): bytesToTokens / categorizeInvocationError /
  deriveSuggestionKind / enum validation throws / deterministic emission /
  context-overflow first-occurrence / probabilistic threshold / tuple
  aggregation / TTL prune / Angle 2 pre-check (2: explicit + 75% default) /
  Angle 1 success / parse-failure aggregation / context-overflow immediate /
  suggestionKind+note overrides / render empty / render structured / render
  multi-symptom

Regression coverage: 24/24 PASS across helper + GoldenPathSynthesizer +
SemanticGraphExtractor.

## Pending Cycle 1 items (separate commit)

- PR body refresh: Contract Ledger for #11447 + explicit
  `mcp-config-template-change-guide.md` compliance section + honest AC closure
  framing (V1 schema NOW delivered; route/emitters were already correct)"
- 2026-05-19T08:36:31Z @tobiu referenced in commit `6f513ac` - "feat(memory-core): Brain-Pillar Consumer-Friction Channel V1 visibility-only (#11447) (#11622)

* feat(memory-core): Brain-Pillar Consumer-Friction Channel V1 visibility-only (#11447)

Graduates Discussion #11444 with Visibility-Only V1 of the Brain-Pillar
Consumer-Friction Feedback Channel. Local-model substrate consumers (e.g.
Gemma 4-31b, Qwen3-8b) can now signal back when the substrate payload is the
wrong shape — context overflow, parse failure, pre-invocation size-precheck
skip, etc. Frictions accumulate in an in-memory aggregator and surface in the
next `GoldenPathSynthesizer.synthesizeGoldenPath` handoff section. V1 is
visibility-only: no `AgentOrchestrator.parseGoldenPath()` routing changes,
no auto-mutation of caller behavior beyond the explicit pre-check skip.

## New substrate (ConsumerFrictionHelper)

`ai/services/memory-core/helpers/ConsumerFrictionHelper.mjs` (323 lines):
- `ConsumerFriction` JSDoc typedef with the consolidated Discussion #11444 schema
- `invokeWithGuardrail(options)` — bidirectional defense wrapper for any LLM
  invocation. Angle 2 (upstream) pre-checks `Buffer.byteLength(inputPayload)`
  against `safeProcessingLimit` (defaults to 80% of `modelContextLimit`); over-budget
  input emits `size-precheck-skip` and skips the invocation. Angle 1 (downstream)
  wraps the invocation in try/catch and categorizes failures into
  `context-overflow` / `parse-failure` / `timeout`.
- `emitConsumerFriction(friction)` — module-singleton aggregator with debounce
  policy: deterministic symptoms surface on first occurrence; probabilistic
  symptoms aggregate by `(assetRef, consumer, symptom)` tuple and surface at
  `PROBABILISTIC_EMIT_THRESHOLD` (3) emissions.
- `getAggregatedFrictions({now?})` — reader with 1-hour TTL pruning; injectable
  clock for deterministic test isolation.
- `renderConsumerFrictionSection({now?})` — pure formatter returning the
  Markdown section; empty string when nothing surfaces.
- `clearAggregatedFrictions()` — test-only state reset.
- `categorizeInvocationError(err)` — exportable categorizer for testability.

Sibling pattern lifted from `EmbeddingProviderConfig.mjs` /
`HarnessClassifier.mjs` (pure functional helpers with injectable deps).

Token-vs-bytes: V1 measures input in bytes (no canonical token-counting
utility exists). Callers pass byte-equivalent `modelContextLimit`. Future V2
may integrate with the Model-Stats registry (ADR 0012).

## Integration into 2 canonical emitters (AC3)

- `ai/daemons/services/SemanticGraphExtractor.mjs` — wraps
  `provider.generate(messages)` inside the 3-retry Tri-Vector extraction loop.
  Pre-check fires per attempt (repair-prompts grow byte size). On engine-level
  failure, retries abort early.
- `ai/services/memory-core/SessionService.mjs` — wraps
  `this.model.generateContent(summaryPrompt)` in `summarizeSession()`. Friction
  `assetRef` = `sessionId` so each session's friction aggregates independently.

## Config substrate

`ai/mcp/server/memory-core/config.template.mjs` adds
`aiConfig.openAiCompatible.contextWindowBytes` (default 131072 — sized for
Gemma-3-4B / Qwen3-8B class ~32K-token context at ~4-chars/token heuristic).

## Handoff rendering integration (AC4)

`ai/daemons/services/GoldenPathSynthesizer.mjs` — `synthesizeGoldenPath()` now
invokes `renderConsumerFrictionSection()` after the capability-gap section
cluster and before the Active PR Cycle State section. Friction section is
omitted when nothing surfaces. Defensive try/catch around the dynamic import +
render call so friction-helper failures don't break handoff rendering.

Ticket scope mentioned `synthesizeHandoff()` but actual method is
`synthesizeGoldenPath()` — section landed in existing method per
substrate-truth.

## Test evidence

`test/playwright/unit/ai/services/memory-core/helpers/ConsumerFrictionHelper.spec.mjs`
(15 tests, all PASS — 859ms): categorize / emit deterministic+probabilistic /
TTL prune / Angle 2 pre-check (2) / Angle 1 success+failure+overflow (3) /
workflowUpdateHint / render empty+structured+multi-symptom (3).

Regression coverage on integrated services:
- `GoldenPathSynthesizer.spec.mjs` → 2/2 PASS
- `SemanticGraphExtractor.spec.mjs` → 4/4 PASS
- `SessionService.spec.mjs` → 7/7 PASS
- Total: 28/28 PASS across helper + 3 integration surfaces

Incidental: pre-existing trailing-whitespace lines in 3 integration files +
config.template.mjs cleaned to clear husky `check-whitespace` pre-commit gate.
Mechanical hygiene; no logic changes on those lines.

## V1 explicitly excludes (ticket scope)

- `MemorySessionIngestor`, `SummarizationCoordinatorService`, `ConceptIngestor`
- `AgentOrchestrator.parseGoldenPath()` task-routing mutations
- Auto-scoring / non-LLM invocation boundaries

## Substrate authority

- Discussion #11444 (graduation source)
- #11441 (Review-Cost Circuit Breaker — symmetric peer)
- ADR 0012 (Model-Stats Framework — V2 tokenization anchor)

* fix(memory-core): Cycle 1 — align ConsumerFriction with Discussion #11444 graduation schema (#11447)

Per @neo-gpt's CHANGES_REQUESTED review (PRR_kwDODSospM8AAAABAUqoyA): the shipped
ConsumerFriction contract drifted from the consensus-bound Discussion #11444
Round-2 + Round-3 graduation schema. This cycle realigns to the substrate-truth.

## Schema drift causes

Cycle 0 used a partial schema (byte-only metrics + free-text suggestion) that I'd
been served via second-hand summary instead of reading Discussion #11444 directly.
Same trust-second-hand-abstraction failure mode as the #11620 plant-time V-B-A
miss earlier today: even when I AUTHORED the consensus draft myself in Discussion
Round-2, I trusted an Explore agent summary rather than re-reading the source.

## Fields added to align with consensus

Per Discussion #11444 comment thread (Round-2 `DC_kwDODSospM4BAnHU` + Round-3
`DC_kwDODSospM4BAnHc`), the V1 schema is:

```typescript
type ConsumerFriction = {
    // Identity tuple (aggregation key: assetRef|consumer|symptom)
    assetRef: string;
    consumer: string;            // service name, not the model alias
    model: string;

    // Symptom + provenance
    symptom: 'context-overflow' | 'parse-failure' | 'token-budget-exceeded'
           | 'semantic-confusion' | 'timeout' | 'size-precheck-skip';
    emissionPoint: 'pre-invocation' | 'post-invocation-failure';
    serviceDomain: 'dream-pipeline' | 'memory-core' | 'concept-extraction' | 'other';

    // Enum-backed structured suggestion (NOT free-text)
    suggestionKind: 'split-document' | 'compress-payload' | 'extract-anchor'
                  | 'reduce-review-cycle' | 'schema-repair' | 'unknown';

    // Token-based durable metrics (bytes retained as evidence)
    inputBytes: number;
    inputTokensEstimate: number;     // Math.ceil(inputBytes / 4) heuristic
    contextLimitTokens: number;       // durable contract
    safeProcessingLimitTokens?: number;

    // Aggregation surface
    firstSeenAt: string;
    lastSeenAt: string;
    count: number;

    // Optional context
    note?: string;
};
```

## Helper rewrite (`ConsumerFrictionHelper.mjs`)

- Added `bytesToTokens(bytes)` exportable heuristic helper
- Added `deriveSuggestionKind(symptom, assetRef)` enum-backed default mapper
- Added enum validation in `emitConsumerFriction` (throws on invalid symptom,
  suggestionKind, serviceDomain, emissionPoint)
- Aggregator entries now manage `firstSeenAt` / `lastSeenAt` / `count` on the
  emitted record itself (cumulative across emissions of the same tuple)
- `invokeWithGuardrail` signature changed to take token-based limits + required
  `consumer` (service name) + `serviceDomain` + optional `suggestionKind` /
  `note` overrides
- `DEFAULT_SAFE_FRACTION` corrected to 0.75 (Round-2 consensus) — was 0.80 in
  Cycle 0
- Render output now shows token metrics + suggestionKind + serviceDomain per
  tuple line

## Config substrate

`ai/mcp/server/memory-core/config.template.mjs`:
- Renamed `aiConfig.openAiCompatible.contextWindowBytes` →
  `contextLimitTokens` (token-based per consensus). Default: 32768 (Gemma-4-31b
  class). Operators tune per deployed model.
- Added `aiConfig.openAiCompatible.safeProcessingLimitTokens` (optional;
  defaults to 75% of `contextLimitTokens` when unset).

This is a Cycle 1 rename of an unshipped config key (added in Cycle 0 of the
same PR), so no operator migration is required — no clone has the old key yet.

## Integration touch-points

- `SemanticGraphExtractor.mjs`: invocation passes `consumer: 'SemanticGraphExtractor'`,
  `serviceDomain: 'dream-pipeline'`, `contextLimitTokens` + optional `safeProcessingLimitTokens`
  from aiConfig, and `note` with retry-attempt context.
- `SessionService.mjs` (`summarizeSession`): same pattern with
  `consumer: 'SessionService.summarizeSession'`, `serviceDomain: 'memory-core'`,
  and `note` carrying `summarizationBatchLimit` provenance.

## Test evidence

`test/playwright/unit/ai/services/memory-core/helpers/ConsumerFrictionHelper.spec.mjs`
rewritten to verify the full schema:
- 18 tests, all PASS (598ms): bytesToTokens / categorizeInvocationError /
  deriveSuggestionKind / enum validation throws / deterministic emission /
  context-overflow first-occurrence / probabilistic threshold / tuple
  aggregation / TTL prune / Angle 2 pre-check (2: explicit + 75% default) /
  Angle 1 success / parse-failure aggregation / context-overflow immediate /
  suggestionKind+note overrides / render empty / render structured / render
  multi-symptom

Regression coverage: 24/24 PASS across helper + GoldenPathSynthesizer +
SemanticGraphExtractor.

## Pending Cycle 1 items (separate commit)

- PR body refresh: Contract Ledger for #11447 + explicit
  `mcp-config-template-change-guide.md` compliance section + honest AC closure
  framing (V1 schema NOW delivered; route/emitters were already correct)

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-19T08:36:31Z @tobiu closed this issue

