LearnNewsExamplesServices
Frontmatter
id12072
titleSub 6: Provider-readiness substrate + rich diagnostic + decay placement
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-opus-ada
createdAtMay 27, 2026, 3:43 AM
updatedAtJun 7, 2026, 7:16 PM
githubUrlhttps://github.com/neomjs/neo/issues/12072
authorneo-opus-ada
commentsCount2
parentIssue12065
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 27, 2026, 4:18 PM

Sub 6: Provider-readiness substrate + rich diagnostic + decay placement

Closed v13.0.0/archive-v13-0-0-chunk-14 enhancementaiarchitecturemodel-experience
neo-opus-ada
neo-opus-ada commented on May 27, 2026, 3:43 AM

Parent Epic

#12065 — Sub 6 of 9. Depends on Sub 3 (executeRemCycle calls into this substrate).

Premise

Per Discussion #12062 §2.3 gaps 3-4: orchestrator's dream task currently has NO provider-readiness gate (only the partial in-DreamService probe which is skipped for gemini/ollama paths) and only emits e.message via recordTaskOutcome instead of a rich diagnostic payload. This is a silent-failure enabler + observability gap.

Per OQ4 [RESOLVED_TO_AC]: GraphService.decayGlobalTopology() placement inside unified REM method as cycle-finalization step (Algorithmic Lock handles cadence). MUST honor §2.10 control-plane safety.

Cycle-1 RESHAPE (operator-VETO 2026-05-27 on prior extraction approach)

The prior Part A prescription — "extract waitForProvider + helpers from runSandman.mjs into a new ai/services/graph/providerReadiness.mjs module" — was the WRONG SHAPE. The extraction encoded contract-violating defaults:

  • DEFAULT_OPENAI_COMPATIBLE_HOST / DEFAULT_OLLAMA_HOST module-level constants
  • config.X?.host || DEFAULT_X_HOST fallback patterns (9 instances)
  • Module-level magic timeout/retry constants (PROVIDER_READY_ATTEMPTS=30, RETRY_MS=1000, TIMEOUT_MS=3000)

These violate the operator's foundational contract enforced at PR #12061 cycle-4: NO HIDDEN DEFAULT VALUE FALLBACKS IN CODE (substrate-banked at feedback_no_hidden_default_fallbacks.md).

The Part A intent — "orchestrator needs provider-readiness capability" — is VALID. The extraction-to-shared-module-with-defaults interpretation is NOT. Reworked prescription below.

Prescription (cycle-1 reshape)

Part A — Orchestrator-side provider-readiness gate + rich diagnostic (config-as-SSOT):

  1. Configurable values live in config templates with concrete defaults — not as module-level constants:

    • ai/config.template.mjs adds an orchestrator.providerReadiness block:
           orchestrator: {
          ...,
          providerReadiness: {
              attempts : 30,
              delayMs  : 1000,
              timeoutMs: 3000
          }
      }
    • MC overlay inherits verbatim (AiConfig.orchestrator.providerReadiness)
    • Env bindings if appropriate (NEO_ORCHESTRATOR_PROVIDER_READY_ATTEMPTS etc.)
    • The existing ollama.host / openAiCompatible.host already exist as concrete config — re-use them; do NOT introduce DEFAULT_* constants anywhere
  2. Orchestrator.executeRemCycle() gates on provider readiness inline before delegating to DreamService:

    • Use existing runSandman.mjs:getGraphProviderReadinessTarget() (post-cycle-4b: config-verbatim, fail-loud) — already substrate-correct
    • Use existing runSandman.mjs:waitForProvider() if it's clean (post-cycle-4b audit pending); OR inline the probe in Orchestrator
    • On failure → emit rich diagnostic via createProviderFailureDiagnostic (or equivalent — see schema below) into healthService.recordTaskOutcome + REM run state model (Sub 2)
    • On unsupported provider → fail-loud (no fallback)
  3. The diagnostic payload accepts config-shaped input verbatim — fields surface as null/undefined when source config is absent (fail-loud); NO || null / || '' coercion in the envelope builder.

  4. If extraction-to-shared-helper is needed for symmetry with Sub 5 (refreshGoldenPath): the helper module accepts config as parameter, fails loud on missing required fields, uses NO module-level default constants. Placement TBD based on actual call-graph (Orchestrator-internal preferred over ai/services/graph/ if only orchestrator consumes it).

Part B — decayGlobalTopology() placement (unchanged from original):

  • Add includeDecay flag to executeRemCycle({...}) signature
  • When true, calls GraphService.decayGlobalTopology() as cycle-finalization step inside the lease
  • 24-hour Algorithmic Lock handles cadence (already in GraphService.mjs — function self-skips when hoursElapsed < 24)
  • MUST enforce §2.10 active-control-plane safety: decay operation MUST allowlist projection-layer edge-types (NOT WAKE_SUBSCRIPTION / mailbox-routing edges)

Contract Ledger

Per ticket-intake-workflow.md §7 — backfilled per @neo-gpt intake-readiness handoff 2026-05-27T05:53Z + reshaped 2026-05-27T~12:30Z for contract compliance.

Diagnostic envelope schema (unchanged from prior — schema was correct, construction was the issue)

Field Type Source Stability
event 'provider_readiness_timeout' | 'unsupported_graph_provider' derived from reason STABLE
reason enum: 'PROVIDER_READINESS_TIMEOUT' | 'UNSUPPORTED_GRAPH_PROVIDER' caller-passed STABLE
provider string resolved graph provider STABLE
graphProvider string alias of provider STABLE
modelProvider string | undefined config.modelProvider raw (NO || null coercion — surface undefined if absent) STABLE
host string | undefined provider host from config; undefined if unsupported (NO || null coercion) STABLE
endpoint string | undefined /api/tags (Ollama) or /v1/models (OpenAI-compat) STABLE
url string | undefined host + endpoint; undefined if host or endpoint absent STABLE
supported boolean true/false per GRAPH_MODEL_PROVIDERS allowlist STABLE
model string | undefined provider model name from config; undefined if absent STABLE
embeddingModel string | undefined provider embedding model from config; undefined if absent STABLE
attempts number | undefined from waitResult STABLE
elapsedMs number | undefined from waitResult STABLE
timeoutMs number | undefined from waitResult STABLE
lifecycleStatus Object | undefined consumer-sourced STABLE
nextAction string actionable English (always present) STABLE shape — English may evolve

Redaction rules (unchanged)

  • apiKey from config.openAiCompatible.apiKey MUST NOT appear in diagnostic envelope. Envelope carries only host, model, embeddingModel, NEVER apiKey.
  • lifecycleStatus is consumer-sourced; consumers MUST NOT pass objects containing credentials.

Contract-compliance constraints (NEW — for implementer)

The implementer MUST audit the diff with the operator's diagnostic command before opening the PR:

rg -n 'cycle-[0-9]|Lane [A-Z]|AC[0-9]|#[0-9]{4,5}|\.mjs:[0-9]+' <changed-source-paths>

Per AGENTS_ATLAS.md §15.6 Source-comment intent filter:

  • NO ticket/PR/lane/AC/cycle/line-number anchors in source-file comments (test.describe labels are the documented exception)
  • NO module-level DEFAULT_* constants substituting for absent config
  • NO || / ?? fallback patterns; config IS SSOT, fail-loud on missing required fields
  • NO magic numbers in module scope; configurable values go in config templates

Part B contracts (DEFERRED to Sub 3 #12069)

includeDecay flag + decayGlobalTopology() cycle-finalization placement: contracts deferred to Sub 3's executeRemCycle({...}) design since the integration point IS that signature. Sub 6 Part B remains blocked-on-Sub-3.

Acceptance Criteria

  • AC1: Configurable timeouts/retries land in ai/config.template.mjs under orchestrator.providerReadiness; MC overlay inherits verbatim
  • AC2: Orchestrator gates dream task on provider readiness before invoking DreamService; uses config-verbatim — NO || fallbacks, NO DEFAULT_* constants
  • AC3: Failure path emits full diagnostic envelope (NOT just e.message) via healthService.recordTaskOutcome + REM run state model (Sub 2)
  • AC4: Diagnostic envelope construction uses config values verbatim; surfaces undefined explicitly for missing fields (no || null / || '' coercion)
  • AC5: apiKey never appears in diagnostic envelope (unit test asserts negative-assertion: expect(diagnostic.apiKey).toBeUndefined() + envelope keys do not include apiKey)
  • AC6: Diff passes AGENTS_ATLAS.md §15.6 diagnostic command — no ticket/AC/line-number anchors in source comments
  • AC7: Sub 5's refreshGoldenPath() (when filed) reuses the same provider-readiness gate — symmetric consumption
  • AC8: Unit tests cover provider-unavailable path (full diagnostic emitted; REM run marked failed with structured failurePhase: 'provider-readiness')

Part B ACs deferred to Sub 3 #12069:

  • AC9 (decay placement) + AC10 (control-plane safety) move to Sub 3 since integration point is executeRemCycle({...}) signature

Avoided Traps

  • ❌ Extract helpers WITH DEFAULT_* constants — operator-VETO precedent (PR #12061 cycle-4 + PR #12084 reject)
  • || fallback on absent config — violates NO HIDDEN DEFAULTS contract
  • ❌ Magic numbers in module scope — configurable values belong in config templates
  • ❌ Source-file comments referencing tickets/ACs/lanes — violates §15.6
  • ❌ Skip the rich diagnostic — cloud-deploy post-mortem loses substrate (per Discussion §2.3 gap 4)
  • ❌ Decay outside the lease — graph mutation outside concurrency protection
  • ❌ Decay without control-plane allowlist — empirical anchor: bridge-cursor failure (Discussion §2.10)

Related

  • Epic #12065
  • Discussion #12062 §2.3 (gaps 3+4+8) + §2.10 (control-plane safety) + OQ4 [RESOLVED_TO_AC]
  • Sub 3 (executeRemCycle consumer), Sub 5 (refreshGoldenPath consumer)
  • Contract anchor: feedback_no_hidden_default_fallbacks.md (PR #12061 cycle-4)
  • Discipline anchor: AGENTS_ATLAS.md §15.6 Source-comment intent filter
  • VETO precedent: PR #12084 rejected 2026-05-27 (9 contract violations + pre-cycle-4b regression)
tobiu closed this issue on May 27, 2026, 4:18 PM
tobiu referenced in commit 4aea5a9 - "feat(orchestrator): provider-readiness gate + rich diagnostic for dream task (#12072) (#12092) on May 27, 2026, 4:18 PM