The load & analysis harness

Analysis & code quality

Every chunk gets a meaning and a risk profile. One tightly-scoped LLM call names what the code is for; deterministic analysers score its structure; and a fleet of background agents turns those signals into human-gated findings and — through a heavily-guarded pipeline — into live pull requests.

Groq intent / layer / pattern analysis

For every chunk the ledger asks one question: "what is this code for, architecturally?" — producing an intent sentence, an architectural layer, and a design pattern. GroqService.AnalyzeAsync(codeSnippet, filePath, repoId) builds a single prompt (MOTMaster-domain preamble, strict JSON schema, a repo+language scope line, up to 6,000 chars) and calls Groq's OpenAI-compatible endpoint (GroqService.cs:79-134). The intent is constrained to name the primary symbol plus a MOTMaster domain concept and forbids generic phrasings, so design embeddings don't collide on vague intents (:104-110). Layer and pattern are coerced to CanonicalLayers / CanonicalPatterns via NormalizeEnum (exact-then-fuzzy), so out-of-set answers fall back cleanly (:47-50,321-334). The parse is defensive — a failure yields a minimal analysis, never a throw (:224-289) — and up to 4 attempts retry on 429, honouring the "try again in Nms" hint with jittered backoff (:129-208).

Live appsettings.json sets Groq:Model to openai/gpt-oss-120b, chosen by measurement on 2026-08-05 (Measured value) — llama-3.3-70b-versatile was the previous incumbent and scored worst of four candidates on recall@1. The code fallback default is llama3-70b-8192. The key is Groq:ApiKey and is hot-reloadable via ReloadCredentials().
Why it matters

This is the single most expensive per-chunk operation and the source of all semantic metadata. One self-correcting, tightly-scoped call keeps spend low while producing the intent that the whole design-vector lane depends on.

Content-hash → analysis cache

Each unique chunk is sent to Groq only once. The chunk text is normalized before hashing, so a pure reformat still hits cache (IngestionEngine.cs:377-381). MetricsService.TryGetCachedAnalysis(contentHash, promptVersion, out analysis) returns the cached intent/layer/pattern and rubric on a hit; SetCachedAnalysis persists only successful analyses (MetricsService.cs:287-326; IngestionEngine.cs:420). The cache is versioned by GroqService.PromptVersion (currently "v5-intent-symbol-domain-nofiller", :41): an entry counts as a hit only if its stored version equals the current one, so bumping the constant forces a one-time global re-analysis.

Why it matters

Frequent re-ingests are essentially free — the paid Groq pass is a one-time cost per distinct chunk — while a single constant bump gives a clean, deliberate way to re-analyse the whole corpus when the prompt improves.

ChunkAnalysis join — chunk classification onto the file-level graph

Classification happens per chunk, but the dependency graph is per file. ChunkAnalysisJoinService.EnrichAsync (:38) runs once per ingest, after the graph merge and before reachability. It scrolls Qdrant payloads (no vectors, no Groq) to build a per-file modal Layer/Pattern map (:54-57) and stamps node.Layer/node.Pattern onto every MergedNode matching by absolute file path, handling /-vs-\ path drift with a secondary forward-slash map (:71-101).

Why it matters

This join is the bridge that lets architectural-integrity features — layer-skip tagging, reachability roots — reason over semantic layers rather than raw symbols.

Layer-skip tagging — the architectural-integrity lens

LayerSkipTaggerService.TagLayerSkips flags edges that jump the stack (UI→Persistence directly). It ranks layers by a configurable order (default UI, API, Business_Logic, Integration, Persistence, Infrastructure, :28-31) and sets MergedEdge.IsLayerSkip=true when the source/target ranks differ by more than LayerSkip:MaxStepDistance (default 1) (:55,95-99). It is direction-symmetric, leaves unknown-endpoint edges untagged to avoid false positives, and is reset and recomputed every run.

Why it matters

It turns semantic layers into an objective, re-computable measure of architectural drift — the raw signal behind the Stack-Integrity lens in the graph viewer.

The code-quality rubric — structural q_* + LLM rubric

Two sources merge into one QualityMetrics. The structural half is deterministic and LLM-free: QualityAnalyserRegistry dispatches by extension to C#/TS/SQL/Generic analysers. The C# analyser is Roslyn-syntax-tree-based and works on snippets — cyclomatic complexity (CsharpQualityAnalyser.cs:59-91), nesting-weighted cognitive complexity (:96-134), LOC, comment density, max nesting and max parameter count (:138-220), plus a normalised-token SHA-256 duplication hash for clone detection (:225-242). The LLM rubric rides the same Groq call: QualityRubricPrompt.Delta is appended when Groq:IncludeQualityRubric (default true), asking for six 1-5 fields (readability, testability, error_handling, single_responsibility, naming) plus a one-sentence risk note. The two are merged at ingest (IngestionEngine.cs:425-437), the model also carrying joined TestCoverageCount and ErrorEmissionCount, and written as q_* payload keys (QdrantService.cs:493-507). A rubric-only backfill patches missing q_readability… for legacy entries (POST /api/admin/rubric/backfill, :2144-2186).

Why it matters

Every downstream agent reads these q_* as cheap, pre-computed risk signals — the shared substrate the whole quality platform, the Quality dashboard, and the learning-needs analysis all stand on.

Pattern signatures — cheap boolean flags

TRUE-only boolean flags describe concrete code characteristics: uses async/await, calls a sproc, has a try/catch. PatternSignatureRegistry dispatches by extension; the C# extractor emits cs_async_await, cs_configureawait_false, cs_try_catch, cs_dapper, cs_ado_sqlcommand, cs_logs_error, cs_calls_sproc and cs_nullable_enabled via cheap string/regex checks (CsharpSignatureExtractor.cs:36-58). Absent means false; the extractor is pure and never throws. Flags are stored on the chunk (IngestionEngine.cs:451-455,501).

Why it matters

A near-free structured fingerprint powers the signature lens of the cohort/inconsistency engine (see Team analysis) and lets anyone filter for a concrete idiom without a scan.

Groq spend / budget guardrails

A hard cap governs total Groq spend. Groq:BudgetUsd (default 25); IngestionEngine.IsBudgetExceeded compares lifetime cost plus the current run's cost against the cap (≤0 means unlimited, :1510-1517). When exceeded, remaining chunks are indexed with a "(budget cap reached — not analysed)" placeholder that is not cached, so raising the budget re-analyses them later (:397-405). Cost is metered per call from token usage times a configurable Groq:Pricing. Crucially the bug-hunt and conversation-review agents honour the same lifetime cap: BugHuntAgentJob.BudgetExhausted mirrors the ingestion gate (:303-308) and records every chat call's spend via RecordGraphifySpend (:274-287); bug-hunt also has a per-run ceiling MaxGroqCallsPerRun (default 150).

Budget-cap placeholders are indexed but intentionally never cached, so they always re-attempt analysis on the next run once the cap is lifted. A budget-throttled run is surfaced on the dashboard "Last Sync" tile.
Why it matters

One dollar-denominated cap governs both ingestion and every LLM-spending agent — closing a former overspend hole and making runaway cost impossible without an operator raising the number.

The agents quality platform — detection agents

A fleet of background agents scans the corpus and raises Findings into a shared queue. Each is a singleton with an opt-in interval loop (mostly OFF by default) plus an on-demand POST /api/admin/agents/<name>/run (Program.cs:1889-1971). Six detection agents:

AgentLLM?SignalFinding type
inconsistency-agent
InconsistencyAgentJob.cs
yes Sweeps (repo×pattern) cohorts through CohortConsistencyService; flags members deviating ≥ threshold (default 0.65) across code/design/naming/signature lenses.inconsistency
bug-hunt-agent
BugHuntAgentJob.cs
yes (heavy) Two-stage: risk-triage q_cyclomatic + q_cognitive − q_test_coverage×5 (+15 if a risk note), then Groq assesses the top-N behind an adversarial refuter panel (default 3 votes, each biased to refute on uncertainty) — a finding survives only on a majority non-refute (:97-131,238-265).bug
test-gap-agent
TestGapAgentJob.cs
no Files with complexity ≥ MinComplexity (10) but coverage ≤ MaxCoverage (0), read from q_*; emits specific, metric-bearing titles.test-gap
dead-code-agent
DeadCodeAgentJob.cs
no Graph orphans/dead-ends per file from ReachabilityService, reconciled against a deterministic source sweep so text-referenced "orphans" (reflection/DI) are suppressed. dead-code
log-scan-agent
LogScanAgentJob.cs
no Per-repo runtime LogFacts (error/warning templates) newer than a watermark; incremental. log-bug
conversation-review-agent
ConversationReviewAgentJob.cs
yes Fetches the Agent repo's real customer conversations via its admin API, asks Groq for UX improvements, and resolves each to a concrete backend file.improvement

A seventh agent, fix-agent, is the actuator (below). Run receipts are AgentRunResult; incremental agents persist watermarks via AgentStateStore.

Why it matters

This is always-on automated code review across the whole estate. The adversarial verifier and the reference-reconciliation sweep are the direct answer to two failure modes — LLM false positives and the graph's forward-only blind spots.

The findings queue — shared, human-gated, self-learning

FindingsCollection stores one point per finding in the Findings collection with a zero 384-d vector (lookups are payload-filter, not ANN) (FindingsCollection.cs:25-56). Identity is stable: the id is a deterministic GUID of repo|path|ruleKey (ComputeId :63-64), so re-detecting upserts in place — and on upsert, agent-owned fields refresh but human-owned Status/PrUrl are preserved, so a re-scan can't reopen an approved or dismissed finding (:73-127). Impact ranking: PriorityFor = severity weight (high 60/med 30/low 10) + graph blast-radius (capped 40), sorted priority-then-recency (:302-310,245-251). The learning loop reads human approve/dismiss history per (agent, rule), penalises priority by the dismiss rate, and once a pattern is dismissed ≥85% over ≥5 samples suppresses new findings of that pattern — never dropping a human-touched one (FeedbackDecision :135-141,88-119). The lifecycle is new → approved/dismissed → in-progress → pr-open → merged, flipped via SetStatusAsync.

Why it matters

One human approval gate governs every agent, and the learning loop stops the queue from refilling with rejected noise — the platform gets quieter as the team teaches it what it doesn't care about.

The fixing dispatcher & fix agent — finding to live code fix

An approved finding can become a draft PR (optionally auto-merged) through a heavily-gated pipeline that defaults to doing nothing dangerous. The dispatcher (Findings/FindingsDispatcher.cs) is a BackgroundService pulling approved findings, running each through the fix agent one at a time, capped at Agents:Fix:MaxPerRun (default 3). It is DISABLED by default (DispatcherEnabled=false, IntervalMinutes=0) and even when enabled honours Git:DryRun (default true). On-demand: POST /api/admin/agents/fix/dispatch?max=&dryRun=. The fix agent (Agents/FixAgentJob.cs) runs a gated chain in RunOnceAsync:

  1. Approval gate — skips anything not approved.
  2. Repo & lock — repo must be a local git checkout; finding flips to in-progress so the dispatcher won't double-pick it.
  3. Plan + blast-radius guard — RefactorPlanGenerator runs; rejected if the in-repo scope exceeds MaxBlastRadiusFiles (default 3).
  4. Caller-safety guard — for interface-changing types, enumerates the complete caller set across ALL repos via ReferenceReconciliationService; rejects cross-repo callers or more than MaxOutOfScopeCallers (default 8) that a single-repo build can't validate (:91-111,251-305) — the safety net for the ~0% inbound-edges era.
  5. Isolated worktree on a fix/<type>/<id> branch.
  6. Scoped engine edit — GroqFixEngine (Fixing/GroqFixEngine.cs) asks Llama at temp 0 for COMPLETE new file contents (never diffs) for only in-scope files, prompted to make the minimal change or decline (ok=false); out-of-scope edits are dropped, applied with path-escape protection.
  7. Drift guard — DriftChecker rejects off-piste, scope-creep changes that left the plan.
  8. Validation — PatchValidator runs the per-repo build+test in the worktree; if none is configured it SKIPS with Ran=false (CI becomes the gate) unless RequireLocalValidation.
  9. Dry-run stop — worktree removed, finding → approved, outcome reports what WOULD change.
  10. Live path — commit → push → gh draft PR → finding → pr-open with the PR URL.
  11. Opt-in auto-merge — Agents:Fix:AutoMerge (default false) squash-merges only after the full green chain (ShouldAutoMerge); a merge failure leaves the draft.

Any rejection or failure reverts the finding to approved (retryable), never to dismissed (human-only). PR bodies carry the finding, rationale, validation, drift verdict, and caller-safety note.

Everything about the fixing path defaults to safe: the dispatcher is off, dry-run is on, and auto-merge is off. Nothing reaches main without passing blast-radius, caller-safety, drift, and build/test — and, by default, a human clicking merge.
Why it matters

It closes the loop detection → human approval → machine fix → PR. Every guard exists because the weaker fix LLM over multi-file edits is treated as untrusted — the guards, not trust, are what let it touch real code.

The Quality dashboard

wwwroot/quality.html is a public per-file risk ranking. The client fetches GET /api/quality?repo=&sort=risk|churn|coverage|complexity&limit=100; the server scrolls Qdrant, aggregates q_* per file, computes Composite = Cyclomatic + Cognitive − Coverage×5 (Program.cs:2585), and surfaces up to three risk notes per file. The sortable table shows Repo, File, Composite, Cyclomatic, Cognitive, Coverage, Churn 90d, Last Modified, and Risk Notes.

Why it matters

It is the human-facing readout of the entire quality substrate — the very same composite the test-gap and bug-hunt agents triage on, so a person and an agent see risk through the same lens.

Open Brain — the MOTMaster Architectural Ledger · generated from source 2026-07-07