You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Free-compute harvester probes run through a bounded pool (FID-2026-0918-001)
The daily harvester (scripts/providers/harvest-freeairouter.ts) probed
every tracked host serially — one host at a time, up to 3 network
round-trips × 10s timeout each — and 41 of 71 tracked hosts sit at boundary-unverifiable, re-probed every run by design. The probe loop
dominated the run's wall clock.
New scripts/providers/lib/probe-pool.ts: runWithConcurrency worker
pool with PROBE_CONCURRENCY = 6 (named constant beside the timeout it
overlaps). Both probe sites wired through it: the daily probe-merge phase
and the Stage-E health loop — the latter split into its own module scripts/providers/lib/health-probe.ts (move-only seam split) to hold harvest-report-phase.ts under the 300-line ceiling. Per-host etiquette
untouched: hosts are distinct vendor endpoints, still one probe set each.
RED-first: 9-pin suite written before the implementation (cap, ordering,
empty, oversized limit, serial degradation, rejection propagation,
constant pin, pooled health ×2). LIVE evidence: full --probe harvest
exit 0 in 13.7s (214 records, ~43 probes) — the serial bound was minutes.
Unverifiable hosts re-probe on a 3-day cadence (FID-2026-0918-002)
The harvester re-probed all 41 boundary-unverifiable hosts every daily
run even though a boundary verdict only needs re-measurement after the
lapse window (PROBE_LAPSED_AFTER_DAYS = 3). New shouldReprobeUnverifiable() gate skips hosts with a fresh lastProbeAttemptUtc (< 3 days) and fail-opens on stale/never/malformed
values — worst case one extra probe, never lost evidence. Every attempted
probe stamps withProbeAttempt() so failed measurements don't re-fire
early.
State layer: optional lastProbeAttemptUtc on CandidateState with
defensive parse — plus an explicit carry-forward line in diffCandidates,
which rebuilds every re-sighted host record from a field literal and
would otherwise have silently dropped the field on every run (a pin
proves a re-sight preserves it).
LIVE double-run evidence: run 1 stamped 40 hosts (14.0s); same-day run 2
skipped every freshly-stamped host (identical host sets, zero timestamp
diffs) in 1.13s. Combined with FID-2026-0918-001 the daily scan drops
from ~14s to ~1.1s typical wall clock, evidence freshness bounded at
3 days by construction.
/model picker now activates the selected provider's routing (FID-2026-0917-005)
Selecting a model in the /model picker persisted the model's provider to settings.json but never activated it in the runtime environment. process.env.DIRECT_PROVIDER and process.env.INFERENCE_BASE_URL stayed
empty, so isDirectProviderMode() returned false — the usage monitor
queried the SavantCode backend (402 → "Out of credits. Please add credits
at https://savant-code.com/usage") and the inference request itself routed
to getWebsiteUrl() (savant-code.com) instead of the selected provider.
The UI said the provider was selected, but requests silently passthrough-ed
to the SavantCode website. A different layer from the OpenRouter key fixes
(FID-2026-0917-003/-004) with the same user-visible symptom; the /provider
picker avoided the bug because it already called activateConfiguredProvider.
The selection logic is extracted into applyModelPickerSelection(model) in cli/src/utils/provider-setup.ts (Law 13 — one function, one truth: resolves model.provider ?? 'openrouter', persists the model preference, and
activates via activateConfiguredProvider, the same guarded seam the /provider picker uses). handleModelPickerSelect is now a one-line
delegation, so the regression suite exercises the real production seam
rather than a copy of its logic. The provider preference is persisted only
when activation declines (unkeyed provider) — no duplicated write on the
happy path.
Fail-closed preserved: an unkeyed provider declines activation and leaves an
explicit INFERENCE_BASE_URL untouched — the SDK surfaces a clear
missing-key error at send time rather than sending a stale credential.
Gates: typecheck cli exit 0; eslint 0/0 on all three files; prettier clean;
model-picker-activation + provider-setup 25 pass / 0 fail (77 expects);
receipt 3/3 PASS re-stamped live at the archived path; independent Verifier
AUDIT PASS on all functional criteria (two findings fixed en route: a
tautological test target resolved by the extraction, and a redundant persist
call removed). Production call-graph confirmed: use-chat-pickers.ts → build-chat-layout-props.ts:181 (onModelPickerSelect) → production chat
UI. Commits 91daaf93 (fix + test) + 6b326d61 (FID record). Closed +
archived 2026-09-17.
Rejected OpenRouter master key now fails closed, not silently (FID-2026-0917-004)
resolveOpenRouterApiKey() treated any failed master-key exchange as a
soft failure: log a warning, then fall through to OPENROUTER_API_KEY.
When that fallback held a stale or revoked key, the CLI silently sent a
dead credential and the operator saw the vendor 401 User not found. at
chat-completions time — an error naming neither the dead key nor the failed
exchange that selected it. Second occurrence of this exact anti-pattern
(FID-2026-0917-001 added the diagnostics but kept the fallthrough).
A 401/403 from the /api/v1/keys exchange is now terminal: it
negative-caches (cachedKey = null) and returns undefined instead of
substituting a different credential for the one the operator configured.
Because openrouter is a registered kind: 'gateway' provider
(common/src/providers/registry.ts:23-25), getModelForRequest's
active-provider guard throws the templated missing-key error before createDefaultInferenceModel is reached — so the operator gets an accurate
message, not a dead key sent upstream. Transient failures (429, 5xx,
network) keep the soft fallthrough, since a still-valid regular key remains
legitimately usable while the exchange endpoint is unavailable.
Returning undefined preserves the existing string | undefined contract
— no new throw sites, no caller breakage. The negative cache reuses the
existing environment-signature invalidation and resetOpenRouterApiKeyCache(), so a fresh key stored via /provider
recovers without a restart.
Gates: sdk typecheck exit 0; resolver suite 13 pass / 0 fail (5 new);
eslint 0 warnings; prettier clean; lint:md exit 0; independent Verifier
AUDIT — 4 PASS / 1 FAIL refuted with registry evidence (the flagged ?? apiKey fallback is unreachable on the live DIRECT_PROVIDER=openrouter
path because the gateway guard throws first). Shipped to the rebuilt sdk/dist the running CLI loads. Closed + archived 2026-09-17.
OpenRouter key shadowed by Bun dotenv auto-loader in dev boot (FID-2026-0917-003)
Every OpenRouter call in local dev failed with User not found. because
Bun's dotenv auto-loader is not disabled by the --cwd .. flag in the
dev script, contrary to the comment at load-dev-env.ts:5-6. With cwd=cli/, Bun pre-seeded process.env.OR_MASTER_KEY from the stale cli/.env.local before load-dev-env.ts ran; the "existing env wins" rule
then permanently locked in that value, so the good repo-root key was
always skipped.
Three-part fix: --no-env-file added to the dev script
(cli/package.json:17) so load-dev-env.ts is the sole deterministic
loader; the false comment replaced with the real mechanism; the divergent
stale OR_MASTER_KEY removed from cli/.env.local (gitignored — a
local-machine fix). The stale OPENROUTER_API_KEY was also cleared from HKCU\Environment.
Bidirectional probe, identical command, one flag different: stale key →
401 → User not found.; root key → exchange OK → HTTP 200. Gates:
typecheck cli exit 0; probe exit 0; quality exit 0. Closed + archived
2026-09-17.
EHEL Law 3 gate no longer deadlocks writes on a lint-failing doc (FID-2026-0917-002)
The EHEL pre-write Law 3 gate blocked every write tool call — including
the exact str_replace that would have fixed the violation — because a
markdown doc (dev/handoff.md) sat in dirtyFiles with a failing
markdownlint (MD040) run keeping it out of verifiedFiles. The violation
blocked its own remedy: a write/verify ordering deadlock. Root cause was a
policy enforced at one lifecycle point and not another: classifyFileKind
already classifies *.md as docs and evaluateWritesAtStepBoundary
already declares "documentation artifacts gate on markdownlint, never on
Law 3" — but the pre-write gate checked only the isExemptWritePath prefix
list (dev/fids/, dev/nova/, dev/scratchpad/), omitting docs/, dev/handoff.md, dev/session-summaries/, and README.md.
One-predicate fix reusing the existing classifier (Law 13 — one function,
one truth): unverifiedDirty now filters to classifyFileKind(f) === 'code'. Code verification is not weakened — a dirty code file still
hard-blocks, and a mixed doc+code set still blocks while naming only the
code file (pinned by a regression test). The Law 15 advisory scanner is
intentionally left unguarded: it reports ALL unverified files, docs
included, as an advisory rather than a block.
Second independent blocker fixed: dev/wiki/patterns/*.md (MD013,
machine-generated 286-char lines) kept repo-wide lint:md at exit 1, so no
doc could ever earn verification credit. Exempted in .markdownlintignore
on the same precedent as dev/scratchpad/** and dev/provider-candidates/**; repo-wide lint:md flipped exit 1 → 0.
Shipped to the built artifact: the fix lives in source AND the rebuilt sdk/dist the running CLI loads (node_modules/@savant-code/sdk symlinks
to sdk/, whose exports map to dist/index.mjs). After bun run build, the
guard appears in exactly one place per bundle — runPreWriteGates at dist/index.cjs:48430 + dist/index.mjs:48334 — with the Law 15 scanner
correctly unguarded. A runtime probe extracted the shipped function body
from the built index.cjs and executed it: docs-only dirty unblocks,
code-only dirty blocks, mixed doc+code blocks naming only the code file.
OpenRouter calls failed with the vendor 401 User not found. Two root
causes, neither in the OpenRouter client (header, base URL, and model id
proven correct by a live probe replaying the exact request path):
dev-mode .env.local discovery used first-match findUp and silently
skipped the repo-root file (a root OPENROUTER_API_KEY never reached process.env), and the resolver's OR_MASTER_KEY exchange fell through
on failure with zero diagnostics — a management key lists models (200)
but cannot run inference.
cli/src/pre-init/load-dev-env.ts now discovers every.env.local
from the repo root down (findAllUp + applyOneEnvLocal), applying
outermost-first so deeper files override; existing process.env values
still win.
sdk/src/impl/openrouter-key-resolver.ts logs the exchange response
status/body (capped 200 chars) on non-2xx before falling through to the
regular-key fallback — a bad management key now says 401 Invalid API key instead of vanishing. No key material is logged (Law 12).
Evidence: live probe — the regular key streams chat-completions 200
through the unchanged client; the management key reproduces the exact User not found. 401. Gates: typecheck cli+sdk exit 0; eslint 0;
prettier clean; load-dev-env.test.ts 4/0; openrouter-key-resolver.test.ts 8/0; quality PASS; receipt 5/5 LIVE;
independent Verifier AUDIT PASS. Closed + archived 2026-09-17.
Compaction signal no longer pins; report excerpt folds (FID-2026-0916-008)
The in-stream "✓ Compaction complete (−N tokens) — P% of window" panel no
longer stays pinned below every later message. onNewUserMessage — the
canonical pre-run zeroing path — now clears compactionStatus, compactionEvents, and lastCompactionReport (preserving compactionCount,
the session sidebar stat). The signal shows for the rest of the run that
compacted, then retires on the next message.
The report excerpt is now collapsed by default behind a ▾ expand
affordance with a 160-char preview, instead of dumping the full
96K-char summary with no toggle. Reuses the existing Button/ CollapseButton pattern from CompactionSummaryBlock (Law 11).
The excerpt renders through a new presentational CompactionReportExcerpt
sub-component that takes the fold state as a prop (same shape as CompactionSummaryBlock's isCollapsed), making both fold states testable
under the static-render harness.
Atria AI (https://api.atria-asi.ai/v1) is a built-in provider exposing
the single Atria-Dawn-Preview model (256K context, vendor-published;
pinned at 262,144 in the fallback table, not derived from the family
heuristic). Follows the bazaarlink one-model static-catalog pattern
(FID-2026-0915-006) exactly: the vendor's /v1/models endpoint is
key-protected, so a static one-model map is the known-checked-in set.
/provider atria selects it and offers the masked ATRIA_API_KEY entry
(setupAvailable: true — derived, matching every other built-in gateway); /model atria/Atria-Dawn-Preview routes through the generic createProviderModel factory (protocol: 'openai', idTransform: 'strip').
Six derived surfaces wired: registry entry (registry-partitioned.ts), atriaModels map + AtriaModel type (gateway-catalogs.ts), MODEL_CATALOGS registration, fallback-table row + NAME_CATALOG_MODEL_IDS,
cli fetchAtriaModels + ATRIA_NAMES (merged in the gateway combine), and
the re-export. Provider docs regenerated (README / cli-release / .env.example tables + docs/index.md + docs/sdk-overview.md).
Gates: typecheck ×3 (common, cli, sdk) exit 0; RED-first pin suite 4/4
(single model cataloged, cli fetcher serves vendor window, fallback-table
pin, registry setupAvailable + ATRIA_API_KEY); openrouter-models family
58/0; Law 4 reachability greps land at gateway.ts:62/:210, openrouter-models.ts:54, static-catalogs-gateways.ts:211; eslint 0;
prettier + lint:md clean. Closed + archived 2026-09-16.
dev/LEARNINGS.md no longer truncates at boot: 1,610 lines / 102,520 chars
→ 810 lines / 45,409 chars. The 27 legacy ## Session narrative blocks
(2026-07-25..2026-08-10) were moved verbatim to a new move-only archive dev/LEARNINGS-RETIRED.md (byte-identity proven against git show HEAD: —
exact substring, normalized equal).
Boundary inversion fixed: the insertion marker sat at EOF, below the
legacy boundary, so every new lesson landed in ungoverned space and learnings:check printed a false PASS (16) over a 32-lesson file. The
marker now sits at line 3 (top of governed space) so appends validate. markerIssues in scripts/learnings-validation.ts enforces marker-above-
boundary and rejects prose trailing after the boundary.
Retire-tool defects fixed in scripts/learnings-retire.ts: an absent --cap parsed argv[0] → NaN, which is not nullish, so the loop retired every entry; and applyRetirement rebuilt the file from lesson blocks
alone, silently deleting all prose and markers. parseCap now rejects
non-finite values; removeLessonSpans removes only retired spans.
Two-tier governance by design: the 16 pre-schema narrative lessons are
preserved below the boundary byte-identically and deliberately unvalidated —
conforming them would require fabricating Evidence/Verification claims
(Law 5). learnings:check output now names both populations so the count
can never be misread as "total lessons."
The /compact standing summary no longer transcribes harness machinery as
operator dialogue. Four root causes fixed at source: ECHO_REFRESH protocol
refreshes are excluded from summary intake (summary-parsing.ts, structured-summary.ts); user-turn transcription strips harness framing
(<system> dumps, <compaction-notice>, <user_message> wrappers, <think>) via stripHarnessFraming (summarize-messages.ts, buildStandingFacts); the existing ≥40% tag-density infrastructure detector
is now applied to every standing-fact candidate line; and substance floors
reject fragment "decisions" (./hosts/ok) and greeting-spam goals
(hey), with operator-ruled greeting-run coalescing (MQ3).
All guards are pure functions in agents/context-pruner/contamination-guards.ts
(300-line-ceiling split from structured-summary.ts, 299 ≤ 300), embedded
into the generated pruner scope via the existing .toString() pipeline —
serialization contract verified.
RED-first pin suite reproduces the LIVE pasted-artifact shapes exactly
(8 fail → 11/0 GREEN); pruner contract suites 35/0; receipt 6/6 LIVE via fid:verify.
Gateway catalogs re-aligned to vendor truth; OpenCode zen/go removed (FID-2026-0916-004)
commandcode catalog re-aligned to the vendor's renormalized roster:
dashed versions (claude-sonnet-4.6 → claude-sonnet-4-6), vendor
prefixes dropped (openai/gpt-5.6-sol → gpt-5.6-sol), zai-org/GLM-5.2 + canonical Kimi casing, plus coding-relevant
additions only (GLM-5.3, grok-4.6, Qwen3.8 family,
deepseek-v4.1-flash class) — no bulk roster dumps. The COMMANDCODE_PROTOCOLS
map was re-keyed to the new spellings.
tokenharbor dead ids removed:gemini-3.6-flash, minimax-m3, and kimi-k3:free (the free channel was a launch promo that has ended —
confirmed LIVE by 429).
opencode-zen and opencode-go removed from Savant entirely. The zen
free-tier gate ("free tier can only be used in OpenCode") is
account-type-based, not UA-spoofable, so zen cannot serve Savant.
Registry entries, catalogs, OPENCODE_ZEN_PROTOCOLS/OPENCODE_GO_PROTOCOLS,
the zen/go cli modules and gateway wiring, context-window rows, the sdk opencode-key-resolver chain and isOpenCodeGoModel, the getOpenCodeGoApiKeyFromEnv getter, and exception-manifest entries are
all gone; test fixtures re-pointed to surviving gateways.
RED-first pin module observed 9 fail / 3 pass against the pre-change
catalogs; receipt stamped 10/10 LIVE (typecheck common/cli/sdk, six
test suites, repo-wide quality). The free-mode cyclic-tool regression
suite (FID-2026-0905-004) was re-pointed to TokenRouter's per-model
protocol ids, preserving all four wire-protocol coverage legs.
Receipt stamping fixed for gates-runs-to-EOF FIDs (FID-2026-0916-003)
EOF-stamped receipts no longer read as stale. A FID whose ## Verification Gates section runs to EOF (no heading after it) got a
receipt that never validated: the stamp's EOF branches rewrite the
document tail (trimEnd + separator reinsertion) while the pre-stamp
fingerprint and the validator's receipt-span removal disagreed on the
trailing-newline shape — a sha256 mismatch, fail-closed. The fix
tail-normalizes the hashed view to exactly one trailing newline inside computeFidFingerprint on both paths (the single hashing authority); stampReceipt and buildReceipt are unchanged, and freshness is
preserved — any content edit still invalidates the fingerprint.
Verified LIVE end-to-end: a gates-to-EOF fixture stamped via the real fid:verify --write chain now passes fid:verify --check (single file
and repo-wide); the fixture was destroyed after the proof. Contract
suites 34/0 + executor suites 35/0; the new fingerprint test module
also carries an EOF stamp-path identity pin alongside the existing
insert-path pin (FID-2026-0907-010).
Catalog window truth for the pre-program static providers (FID-2026-0916-002)
The four static-catalog providers that predate the context-window
program — tokenrouter, tokenharbor, commandcode, opencode-go — now
resolve windows from vendor truth: 94 exact-id rows added to CONTEXT_WINDOW_FALLBACKS from keyed LIVE rosters (bazaarlink +
orcarouter, OpenRouter tiebreak; 2-of-3 vendor votes marked), and all
four CLI catalog fetchers switched from the inferContextLength family
heuristic to getContextWindowFallback(id). Fixes the operator-reported tokenharbor/deepseek-v4-flash 131k display (vendor-published:
1,048,576). A closed-world coverage invariant pins every curated static
id to a table row so this gap class cannot silently reopen.
TokenRouter dead channels removed (catalog 35 → 31): both free GLMs
mirothinker (keyed 503 "No available channel") and image-only
seedream. MiniMax-M3 kept — keyed HTTP 200 despite roster absence
(roster absence ≠ dead, verified by call).
Per-model protocol dispatch: new TOKENROUTER_PROTOCOLS map (vendor supported_endpoint_types) + registry protocol: 'multi' wiring — 6
Responses-only, 7 Anthropic-only, and 1 Gemini-only ids were
mis-dispatched as chat before (codex → HTTP 404 "Use the
v1/responses endpoint instead"). OrcaRouter free-tier cap + OpenRouter
key-rotation notes documented. 39/39 tests across 3 suites (825
expects); LIVE window-truth probe 17/17 vs the keyed rosters.
Pipeline integrity hardening (FID-2026-0916-001)
Denylist round-trip fixed:mergeDenylist normalizes both persisted
shapes (the {_meta, entries} object it writes AND legacy bare arrays)
and preserves firstSeenUtc while bumping lastSeenUtc — the denylist
no longer resets every run. LIVE proof: 118 → 163 entries across three
harvests with all pre-FID rows keeping their original firstSeenUtc; flagged rows persisted for the first time.
Silent-class audit trail: stage-0 exclusions beyond
risky/down/free-relay (unconfirmed category, monitor-directory,
free-product, no-probe-data, unreachable) now render counted report
groups + a summary row via a new pure buildExclusionAuditRows builder
(legacy three classes byte-identical) — the report's "every gate
decision, with reason" claim now covers the whole feed.
providers:propose fail-closed tracking guard: a host absent from
the tracked candidate set refuses to scaffold (exit 1 + remediation:
run harvest first / use an operator seed); tracked hosts keep the
DO-NOT-CURATE warning path.
Probe trust boundary designed, not accidental:probe-endpoint
admits only https URLs with public hosts — static checks reject
loopback/private/link-local literals and non-canonical integer/hex/octal
host forms, and a resolving DNS check (injectable lookupImpl) closes
the hostname-based SSRF gap; the operator-stamped Stage-E health path
passes allowPrivate: true (local Ollama custom providers remain
health-checkable). Suite 85 → 101 (16 new pins), 411 expects, 0 fail;
LIVE harvest receipt 5/5.
BazaarLink gateway provider (FID-2026-0915-006)
19th provider, static 12-model catalog: the identity-audited genuine
free channel bazaarlink/qwen/qwen3.7-flash:free (1M ctx, keyed gauntlet:
consistent EN/ZH self-ID, zero injection class) + the 11 gateway-available
top-coding paid models (BenchLM SWE-bench Pro leaderboard dated
2026-09-15 ∩ the LIVE keyed roster; fable-5.1/fable-5/opus-5/opus-4.8/
opus-4.7/sonnet-5 @ 1M, gpt-5.6-sol/terra/luna @ 1.05M, grok-4.5 @ 500k,
qwen3.8-max @ 1M). Paid channels added untested per operator ruling;
serving-name disclosure (responses echo the routed model) is the carried
honesty control.
Excluded by audit evidence, not omission: deepseek :free channels
(EN self-IDs claimed GPT-4 and Claude Opus 4.1; ~60–70 hidden injected
tokens) and auto:free (429 storm, 120–265s latency) — reinstating them
requires a new operator decision plus a gauntlet re-run.
Full integration chain: registry entry (openai-compatible, strip
transform, BAZAARLINK_API_KEY), MODEL_CATALOGS ref, shim export,
vendor fallback table, CLI picker fetcher + gateway merge, 8 docs
surfaces; Step-5 LIVE keyed round-trip through the production chain
(exact echo, no injection class); closed-world pins widened to 19
providers / 17 setup.
Operator seed intake: 16 operator-authorized hosts enter the discovery pipeline (FID-2026-0915-003)
New scripts/providers/lib/seed-hosts.ts: the operator's free-provider
expansion list (16 hosts after rulings) as typed SeedCard data, merged
into the stage-0 candidate stream each harvest run with operator
precedence — an operator-authorized seed overrides the feed's card for
the same host (the original feed-precedence design was corrected after
the first LIVE run exposed it silently defeating two rulings).
Identical gate chain, no bypass: seeds pass stage-0, the typosquat
screen, registry dedupe, and the LIVE unauthenticated-generation probe
exactly like feed hosts — which rejected 2 of the 16 (b.ai,
platform.experientiallabs.ai: open relays, LLMjacking class) with
evidence recorded in the denylist and report.
LIVE result: 16/16 seeds tracked in candidates.json (8 boundary-ok,
6 boundary-unverifiable pending deeper discovery, 2 rejected); 14
first-sight provenance rows in the report audit trail; seed pins 6/126
(pipeline suite 82/342, 0 fail); removal = delete the seed entry.
File-cap split program closed (FID-2026-0915-002): all 12 over-cap
files split move-only on existing seams; quality:report PASS (1498
files); archived with verification receipt.
File-cap split program: all 12 over-cap files split on existing seams (FID-2026-0915-002)
bun run quality:report now PASSes with 0 violations (1498 baselined
files): every file above the max_file_lines: 300 absolute ceiling was
split move-only along a seam that already existed — no behavior change, no
import-path churn for consumers (re-export facades / same-name exports).
Splits:discovery-state.ts → intelligence + io + facade; harvest
main → candidates-io + phases + report-phase; report.ts → format +
sections; harvest-core / intelligence-layer test suites split on
describe boundaries with exact assertion parity (76/216); the
context-pruner trio → preserved-state-merge / summary-sections / summarize-messages-entries via the embeddedHelpers Batch B pattern
(registered in handle-steps.ts; serialization tests + bundle
regeneration green); right-sidebar → header component; run-results →
gates module; spawn-agent-inline → pruner-guards module; provider-wizard-steps → step-sections module.
Operator visibility: commit series 67e77d37 → 9fbe5364; FID
resolution records the measured seams and the two recorded deviations.
Free-compute intelligence layer: stability · model index · churn · nudges · fallback hints · quality gauntlet (FID-2026-0915-001)
Six workstreams over data the pipeline already collects (operator:
"so all 6"; MQ1–MQ4 ruled — all four recommendations accepted: 14-day
ring, <½-latency nudge, 8-prompt rubric, gitignored denylist):
W1 stability ring — every host accumulates a 14-sample daily history
in candidates.json (state v2, zero-blip migration; same-day re-runs
replace, oldest dropped); the report's candidates tables gain an Uptime (14d) column (100% · 67ms avg).
W2 model-first index — family normalization joins vendor ids
(meta/llama-3.3-70b-instruct), feed prose (Llama 3.1/3.3 family),
and bare ids (llama-3.3-70b); the report gains a readiness-ordered Model availability index (LIVE: deepseek ×23, qwen ×20, llama ×16),
and a host silently dropping a model family is flagged in the audit
trail (dropout detection).
W3 churn analytics + denylist — firstSeenUtc is set on first
sight and never mutated; the report gains an Ecosystem churn
section (7-day new-host window, 72h-rule lapses, median lifespan of
dead hosts); denylist.json accumulates every rejection/flag ever
recorded with dates (machine-readable, gitignored).
W4 better-default nudges — when a health-tracked pipeline provider
degrades AND a boundary-ok candidate is more than 2× faster with ≥1
model, the sanitized agent announce block gains ONE capped suggest-line
(agent suggests, never switches).
W5 429 fallback hints — on a rate-limit failure the CLI offers up
to 2 OTHER boundary-ok hosts serving the same model family
(fail-silent: missing state ⇒ banner exactly as before; once per model
per session; the failing host is excluded via the error URL; wired
through both the throw path and the error-output completion path).
W6 quality gauntlet (operator-run) — bun run providers:quality -- <host> runs a fixed 8-prompt coding rubric against one candidate with
the operator's key (env at invocation, used in headers only, never
stored or logged, refused BEFORE any network call); scores land in quality.json and render in the report's Quality (operator-run)
section. Never scheduled; not a merge gate.
State layer moved to common
(common/src/providers/discovery-state.ts — Law 13 single truth for the
report, the wizard prefill, and the W5 CLI seam; scripts keep a
re-export shim). Invariants unchanged: zero auto-merge/switch, zero key
storage, agent has no destructive tool, artifacts gitignored.
Verification: 34 new pin tests (RED-first per workstream; 131 tests
across touched suites, 0 fail); typecheck ×4; eslint --max-warnings 0;
lint:md; prettier; LIVE harvest gate — v2 migration zero-blip (55/55
unchanged), ring/index/churn/quality sections rendered, chutes.ai
still RELAY ✗ with history. W6 validated live (orcarouter.ai,
operator key, 8/8 · 1607ms): caught a fetch POST→GET redirect defect
(apex→www 301) — gauntlet now normalizes /v1 and re-POSTs manually with
auth intact, and probeEndpoint was hardened the same way (RED-first
pins; the harvester re-probes boundary-unverifiable hosts so stale
measurement artifacts self-correct — LIVE: orcarouter.ai corrected from boundary-unverifiable to boundary-ok (401, 389ms)).
Daily pipeline (bun run providers:harvest [--probe]): fetches the
freeairouter open feed once (transparent UA, 30s timeout, once daily),
applies the stage-0 filter (verified + reachable + confirmed
first-party-free/commercial-aggregator — 55/214 viable on the live
payload), runs the two-tier typosquat screen (tier-1 auto-reject with a
legitimate-vendor allowlist — caught api.celebras.ai; api.z.ai
survives), diffs against persisted state + the built-in registry (72-hour
lapse prune via downStreak), and — with --probe — probes new
candidates against their own vendor surfaces (GET /v1/models +
401-boundary check: unauthenticated 2xx = open relay = auto-reject; no
keys ever) and health-probes pipeline-sourced custom providers.
Stable data-backed report (MQ8):dev/provider-candidates/report.md
(gitignored) is replaced in place every run — full candidate table,
complete audit trail (every exclusion/rejection carries a reason),
new/changed/lapsed sections, Stage-E health section. candidates.json
carries the rolling diff state; agent-context.txt carries the announce
payload.
Stage C (bun run providers:propose -- <host>): writes an
FID-001-shaped curation scaffold into dev/scratchpad/provider-proposals/
(write-guarded to dev/); open-relay hosts get a DO-NOT-CURATE banner.
Loop-5 production seams:CustomProviderConfig gains a fail-closed-
validated provenance stamp (source: 'discovery-pipeline' + acceptedAt, preserved by parseCustomProviders — single validation
truth); the CLI boot-check runs the harvest in the background when the
report is >24h old (repo-gated via isDiscoveryRepo, fail-silent, never
blocks the TUI, IS_SAVANT_FREE exempt); session bootstrap surfaces the
sanitized announce block once per candidate set (host/count/verdict/
report-path facts only — never feed prose; prompt-injection surface zero
by construction).
MQ6 one-step add:/provider add <host> for a probe-passed candidate
opens the existing wizard pre-filled (id/label/baseUrl/envVar derived
from the host; Enter adopts, typing replaces — the consent gesture exists
only on discovery sessions); the finalized record is stamped; open-relay
hosts refuse to prefill (enforced after the LIVE run caught a real one).
Stage E lifecycle close: stamped providers are re-probed daily
(liveness, /v1/models shape, 401-boundary drift = compromise
signal); degradation surfaces in the report + agent block with the
exact remediation — the shipped /provider remove <id>. The agent never
receives a destructive tool.
LIVE gate: first real run — 214 feed records → 55 candidates → a
genuine open relay (chutes.ai) auto-rejected in the wild; 7 tier-2
flags with exact distance evidence. 54 new pin tests (RED-first for the
wizard seam); suites green: cli 3696 / common 724 / sdk 611, typecheck
×4, eslint --max-warnings 0, lint:md, prettier. Day-two verification
runs caught and fixed probe-evidence erosion (standing boundary verdicts
now carry forward across unchanged re-sights — re-verified live, 55/55
verdicts survive; zero classification drift and a silent no-nag announce
on unchanged feeds).
Report/state restructure (operator review: "professionally structured,
properly organized models"): the report gained an executive Summary
table, a "This run's changes" section (only when non-empty), candidates
grouped first-party/aggregator and sorted by readiness (verified
boundary → model count → latency, relays last), boundary display as ok/unverified/RELAY ✗ from the STANDING verdict, and a capped
per-host model roster; the audit trail became counted reason-groups
(4 sections replace ~140 bullets; every host still listed). The Models
column no longer conflates "list hidden" with "no models": probed count
plain, feed-listed count with a † marker, else —. The fingerprint is
MODELS-ONLY (the feed's LLM-rewritten quota prose caused false changed classifications; live proof: the prose-hash run misflagged 2
hosts the models-hash run correctly ignores), stored as an 8-hex
FNV-1a hash in a _meta+hosts candidates.json wrapper (~44% smaller,
one-glance records) with bit-identical legacy migration — zero
re-classification blip. Renderer/serializer logic moved out of the
driver into tested lib modules; 62 pipeline tests green.
The compaction artifact the operator hit (Progress note: fragment
storms, [digest] bytes=11 HEAD: [compacted] re-digest spam, punctuation
"decisions", error-log self-play) was fully diagnosed: the summary was
authored by a deterministic TypeScript transcriber
(agents/context-pruner/*, embedded model-less via .toString()), not
by a model. All 21 reference compact systems reviewed (openclaw, kimi,
hermes, codex, cline, …) use a model call.
LLM semantic writer (new Layer 3): the session model rewrites the
deterministic excerpt into a first-person handoff (kimi pattern:
settled-vs-open decisions, exact commands/paths/results, named unknowns,
forward plan, evidence honesty) at the spawn boundary — the only seam
with model access. Output is validated (800–6000 tokens, no tool-call
leakage, secrets [REDACTED]); user aborts propagate; every other
failure degrades to the deterministic excerpt verbatim. Session model
only — no override knob (operator ruling).
Deterministic writer hardened (kept as fallback + input material):
consecutive streamed assistant fragments coalesce into one entry (the
2-char-line storm is dead), identical consecutive user messages
coalesce with an (×N) marker, [compacted] sentinels and {compacted:true} JSON placeholders are never re-digested, the
error-log section is removed from the structured block, preserved-state
paths are normalized repo-relative from the threaded projectRoot and
recency-capped at 12 newest-first (was 25, absolute C:\... noise), and
the first-user-turn pin skips protocol dumps. Edits land in agents/context-pruner/* and regenerate the bundled artifact — the
generated file is never hand-edited.
Context-window truth: both substring heuristics (name-table guess
inferContextLength fallbacks, including two live in the gateway
catalogs found by the audit) are retired to a vendor-published CONTEXT_WINDOW_FALLBACKS table; resolveContextWindowForModel
returns { window, provenance } and the sidebar now shows a window
provenance badge (catalog / gateway / vendor-table / default). Unmatched
models get the conservative default — never a wrong family guess.
Envelope preservation is pinned across every compaction form
(<conversation_summary> → <historical_memory> → <compaction-summary>, plus the bare literal) so emergency truncation
can never drop the preserved state. A LIVE gate drove the production runContextPrunerMain over a 190-message fragment-storm history:
150 fragments → 2 entries, 11 resumes → 1 coalesced entry, zero
placeholder re-digests, goal + first turn pinned verbatim (7/7).
Gates: typecheck × 4, agents/agent-runtime/sdk/cli suites (all
pre-existing vendored-resource and cross-suite-pollution failures
proven pre-existing by stash A/B), eslint --max-warnings 0,
prettier, lint:md, regenerated-bundle verification, LIVE compaction
gate.
Two new built-in gateway providers follow the static-allowlist template
(FID-2026-0913-001): Infron (catalog api.infron.ai/v1/models,
inference https://llm.onerouter.pro/v1; 4 :free + 5 top-coding
models) and UnoRouter (inference https://api.unorouter.com/v1; 12
curated :free + 5 top-coding models, 5 of them live-verified HTTP-200
this session). Codex/Responses-API-only models are excluded by operator
ruling (chat-only). 26 curated channels total, all context windows
vendor-published; picker derivation, setup derivation, and model prefixes
all derive from the two registry entries with no new machinery.
The CLI picker's static-catalog module split (283/300 cap discipline):
the new providers' maps + fetchers live in cli/src/utils/openrouter-models/static-catalogs-gateways.ts, leaving static-catalogs.ts untouched. Test-suite splits mirror the Task 45
pattern (provider-registry-gateways.test.ts, model-provider-free-mode-gateways.test.ts); closed-world pins widened
16→18 registry keys / 14→16 setup ids.
Latent harness defect fixed: the SDK's one-shot .env.local bootstrap
("Using environment: dev") fires lazily on the first transitive import,
re-injecting real keys AFTER a test harness deletes them in beforeEach — key-missing fail-closed legs must warm the module graph
first. The fix also cured a pre-existing test-order flake in the sdk
family (proven by stash A/B: failures 6→5, errors 4→3).
Docs synced across all 8 hand-maintained surfaces the drift guard pins
(README ×2, docs ×4, release README, .env.example); generate:provider-docs:check green.
Known boundaries: Infron's free tier requires team balance ≥ $5, so its
LIVE keyed 200 remains NEEDS-REVIEW until the account is funded
(OrcaRouter precedent — the integration itself is verified end-to-end
through the production chain); UnoRouter documents 429s at peak hours
and multi-supplier failover (personal-use posture, not a community
default).
OrcaRouter gateway provider closed — keyed live acceptance green (FID-2026-0911-002)
The final Step-4 closure gate passed: with a fresh post-linkage key,
the keyed chat round-trip returned HTTP 200 "OK" on orcarouter/free (catalog 195/195 via the production chain, no-key
401 fail-closed). No code changes in this closure — the integration
had been complete since 2026-09-11; the residue was an
account-entitlement state. Root cause of the two-day 429 err_free_access_denied era: OrcaRouter binds free-tier entitlement
at KEY-MINT time, so the pre-linkage key kept failing after the
GitHub linkage registered (hypothesis (a) confirmed by
discriminator). FID closed + archived with the receipt re-stamped
live at the archived path (8/8 gates).
Provider docs synced to the 16-provider registry + drift guard (FID-2026-0913-005)
Post-0.0.31 sweep: the top-level README provider table (9 rows against a
16-provider registry — missing KiosAPI, APInex, OrcaRouter, B.AI, HCNSec,
TokenBom), the zh-CN mirror, docs/index.md, docs/sdk-overview.md, and
the shipped npm README's shell examples (still teaching the legacy OPENCODE_GO_API_KEY) are all synced to the registry; 4 dead docs/design/Adding New Providers.md links re-pointed to docs/archive/design/.
The generator now owns a THIRD GENERATED surface — the main README's
provider table — and --check additionally runs scripts/provider-docs-drift.ts: presence-token pins over every
hand-maintained surface (8 windowed + 5 whole-file checks), so the next
provider that lands without its doc sync fails validate:repository,
pre-push, and the release gates with the exact missing token (69 findings
pre-fix → 0).
RED-first test scripts/__tests__/provider-docs-drift.test.ts (4 tests /
45 expects) pins the current tree, a stale fixture, and missing anchors.
Latent defect fixed en route: GENERATED markers between a markdown
table's delimiter row and body rows terminate the table token — the
release README had shipped that broken shape since the generator's
introduction, masked by its line-1 file-wide MD013 disable. The generator
now renders the ENTIRE contiguous table inside the markers
(renderProviderTable).