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).
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().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.
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).
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.
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).
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).
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).
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:
| Agent | LLM? | Signal | Finding type |
|---|---|---|---|
inconsistency-agentInconsistencyAgentJob.cs | yes | Sweeps (repo×pattern) cohorts through CohortConsistencyService; flags members deviating
≥ threshold (default 0.65) across code/design/naming/signature lenses. | inconsistency |
bug-hunt-agentBugHuntAgentJob.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-agentTestGapAgentJob.cs | no | Files with complexity ≥ MinComplexity (10) but coverage ≤ MaxCoverage (0),
read from q_*; emits specific, metric-bearing titles. | test-gap |
dead-code-agentDeadCodeAgentJob.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-agentLogScanAgentJob.cs | no | Per-repo runtime LogFacts (error/warning templates) newer than a watermark; incremental. | log-bug |
conversation-review-agentConversationReviewAgentJob.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.
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.
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:
- Approval gate — skips anything not approved.
- Repo & lock — repo must be a local git checkout; finding flips to
in-progressso the dispatcher won't double-pick it. - Plan + blast-radius guard —
RefactorPlanGeneratorruns; rejected if the in-repo scope exceedsMaxBlastRadiusFiles(default 3). - Caller-safety guard — for interface-changing types, enumerates the
complete caller set across ALL repos via
ReferenceReconciliationService; rejects cross-repo callers or more thanMaxOutOfScopeCallers(default 8) that a single-repo build can't validate (:91-111,251-305) — the safety net for the ~0% inbound-edges era. - Isolated worktree on a
fix/<type>/<id>branch. - 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. - Drift guard —
DriftCheckerrejects off-piste, scope-creep changes that left the plan. - Validation —
PatchValidatorruns the per-repo build+test in the worktree; if none is configured it SKIPS withRan=false(CI becomes the gate) unlessRequireLocalValidation. - Dry-run stop — worktree removed, finding →
approved, outcome reports what WOULD change. - Live path — commit → push →
ghdraft PR → finding →pr-openwith the PR URL. - 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.
main without passing blast-radius, caller-safety, drift, and
build/test — and, by default, a human clicking merge.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.
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