Frontier event log, April 29 to April 30, 2026

What a consumer Mac app does on the day five frontier LLM events land in a 48-hour window

Between April 29 and April 30, 2026, three labs shipped a model and one retired a context tier. The visible result inside Fazm, an open source Mac agent, came down to two short Swift functions and a migration ladder. This page traces the code path end to end.

M
Matthew Diakonov
12 min

Direct answer, verified 2026-05-18

Models released or retired April 29 to 30, 2026

  • April 29: IBM Granite 4.1 30B (Apache 2.0, 131K context, AA Intelligence Index 14.7). Alibaba Qwen3.6 35B-A3B, Qwen3.6 Plus, Qwen3.6 Max Preview.
  • April 30: xAI Grok 4.3. Anthropic retired the 1M-token context window beta for Claude Sonnet 4.5 and Sonnet 4.

Cross-checked against llm-stats.com/llm-updates and the vendor announcements summarised at llmgateway.io/timeline.

36h

Streamlined GPT model picker to show only the latest generation (GPT-5.5). Hide 1M-context model variants in built-in mode to prevent picking a tier without entitlement. Auto-fall-back to standard context when Claude rejects a request for the 1M-context tier.

CHANGELOG.json, Fazm 2.7.1, 2026-05-01

The 48-hour window, event by event

The narrow read is that April was a busy month for model releases (true: at least nine significant models from six labs in the first half). The useful read is what happens on a single user's machine when four labs land four shipping or retirement events in two days. Here is the local view of the window.

April 29 - IBM Granite 4.1 30B

IBM Research shipped Granite 4.1 30B under Apache 2.0. 131K context, AA Intelligence Index 14.7. The first release of the 48-hour window and the first Apache-2.0 frontier model of the month after Gemma 4. No app on your Mac picks it up by default; you bring it via a bridge.

April 29 - Qwen3.6 35B-A3B

Alibaba pushed three at once: Qwen3.6 35B-A3B (35B total / 3B active), Qwen3.6 Plus, Qwen3.6 Max Preview. The A3B variant is the one that actually runs on a Mac with 36GB unified memory; the other two are API-shaped.

April 30 - xAI Grok 4.3

xAI released Grok 4.3 the next morning. Not an Anthropic-shaped endpoint, so it does not surface in a Claude-Code-style picker. Reachable via a custom endpoint that translates Anthropic /v1/messages to xAI's API.

April 30 - Anthropic retires 1M-context Sonnet beta

Same day, Anthropic ended the 1M-token context window beta for Claude Sonnet 4.5 and Sonnet 4. Anyone with sonnet[1m] persisted in their settings now has an entry pointing at a tier the SDK no longer reports.

Fazm 2.7.1 - May 1

Next ship after the retirement. Three CHANGELOG lines cover it: 'Streamlined GPT model picker to show only the latest generation (GPT-5.5)', 'Hide 1M-context model variants in built-in mode to prevent picking a tier without entitlement', and 'Auto-fall-back to standard context when Claude rejects a request for the 1M-context tier'. No settings dialog, no manual migration.

A roundup is allowed to stop here. A consumer Mac app is not. The three model launches each demand a different ingress path (acp-bridge natively handles Claude, codex-acp natively handles GPT, everything else routes through a Custom API Endpoint), and the Sonnet 1M retirement creates orphaned entries in any user's persisted settings. The next three sections trace what Fazm actually does on your Mac during that window.

Step 1: the picker repopulates without an app update

The picker is not a static array. It is a derived view of two streams (Claude models from acp-bridge, Codex models from codex-acp), filtered and merged at runtime. Every time either stream reports a new list, the recomputeAvailableModels() function at ShortcutSettings.swift:289 fires. The merged list replaces the @Published availableModels array, SwiftUI rebinds, and the picker changes shape. No App Store push, no settings dialog, no user action.

Here is the JSON-RPC frame to picker UI path verbatim, with the functions that fire on each hop labelled inline.

ACP frame to picker UI, on the next message after a model event

AnthropicSDK / codex-acpACPBridge.swiftShortcutSettingsPicker UIavailable_models frame (JSON-RPC)updateModels([(id, name, description)])filterContextVariants + merge with CodexrecomputeAvailableModels (line 289)saved selection missing? -> migration ladder@Published mutation, SwiftUI rebindsuser opens picker, sees frontier-only list

The hops marked event are self-messages: filterContextVariants(), recomputeAvailableModels(), and the migration ladder all run inside ShortcutSettings without leaving the actor. Once the @Published mutation hits availableModels, SwiftUI does the rest. The pill on the floating bar rebinds on the same frame.

The three things recomputeAvailableModels does, in order

1

Step 1 - the merged list rebuilds

recomputeAvailableModels() at ShortcutSettings.swift line 289 runs every time either acp-bridge or codex-acp reports a fresh list. It concatenates filterContextVariants(lastClaudeModels) with lastCodexModels, dedupes against the current availableModels, logs the diff, and only then mutates the @Published array. The picker UI rebinds automatically.

2

Step 2 - the version floor filters older GPT tiers

isPickerEligible(modelId:) at CodexBackendManager.swift line 190 runs on each Codex model id. It strips variant suffixes ('-mini', '-codex'), parses the base 'gpt-X.Y', and returns true only when (major > 5) or (major == 5 and minor >= 5). gpt-5.4/high silently disappears the next time codex-acp probes; the user does not have to do anything.

3

Step 3 - the same-effort migration picks the replacement

If the user's saved selectedModel is no longer in the merged list, ShortcutSettings.swift lines 296 to 323 try four rules in order: (a) normalize the alias ('opus' -> 'default'), (b) drop a [1m] bracket suffix that just got filtered out, (c) for GPT ids, run preferredGptModel(in:sameEffortAs:) at line 329 to find the same effort tier on the newest generation, (d) longest-prefix match. The pill on the floating bar keeps its label.

Step 2: the version floor at CodexBackendManager.swift:190

The actual rule that decides whether a Codex model survives the next picker rebuild fits on the screen. It is 16 lines. It does not look at benchmarks. It does not call out to a remote model registry. It parses the model id, strips variant suffixes, and compares the base version against a hardcoded floor of gpt-5.5. Anything older drops out the next time codex-acp probes.

// /Users/matthewdi/fazm/Desktop/Sources/CodexBackendManager.swift
// Lines 186 to 205. The literal frontier filter.

/// Returns true when the modelId belongs to the current frontier generation
/// the picker should expose (gpt-5.5 or newer). Older generations like
/// gpt-5.4, gpt-5.3-codex, gpt-5.2 are hidden once a newer generation works.
/// Inputs look like "gpt-5.5/high", "gpt-5.4-mini/low", "gpt-5.3-codex/high".
static func isPickerEligible(modelId: String) -> Bool {
    let family = modelId.split(separator: "/").first.map(String.init) ?? modelId
    // Strip variant suffixes ("-mini", "-codex") so we only compare base version
    let base = family.split(separator: "-").prefix(2).joined(separator: "-")
    // base is "gpt-5.5", "gpt-5.4", etc. Extract major.minor.
    guard base.hasPrefix("gpt-") else { return false }
    let version = String(base.dropFirst("gpt-".count))
    let parts = version.split(separator: ".")
    guard parts.count == 2,
          let major = Int(parts[0]),
          let minor = Int(parts[1]) else { return false }
    // Keep gpt-5.5 and newer (e.g. 5.5, 5.6, 6.0)
    if major > 5 { return true }
    if major == 5 && minor >= 5 { return true }
    return false
}

Three structural choices worth flagging. First, the parser ignores anything after the second hyphen in the family, so gpt-5.5-mini and gpt-5.5-codex collapse to the same base for the version check. The variant survives if the base survives. Second, the rule is open ended: when OpenAI ships gpt-5.6 or gpt-6.0, the existing binary accepts the new model on the next probe with no code change. Third, the floor is a constant. Bumping it (say, to gpt-5.6) is a one-line edit; the next Fazm release inherits the new floor without touching the picker UI.

Step 3: the migration ladder at ShortcutSettings.swift:296

The filter is half the story. The other half is what happens when a user's saved selectedModel gets filtered out. The migration ladder runs as a four-rung if-else chain immediately after the @Published mutation:

// /Users/matthewdi/fazm/Desktop/Sources/FloatingControlBar/ShortcutSettings.swift
// Lines 296 to 323. Four rules tried in order.

// If the current selection vanished, try to migrate it within the same
// backend (Claude alias normalization or longest-prefix match).
guard !merged.contains(where: { $0.id == selectedModel }) else { return }
let normalizedSelection = Self.normalizeModelId(selectedModel)
if merged.contains(where: { $0.id == normalizedSelection }) {
    selectedModel = normalizedSelection
    log("ShortcutSettings: normalized selectedModel to \(normalizedSelection)")
} else if selectedModel.contains("[1m]") {
    // User had a [1m] variant saved but it's been filtered out (likely switched
    // to builtin mode where 1M context isn't available). Strip [1m] and retry.
    let stripped = normalizedSelection.replacingOccurrences(of: "[1m]", with: "")
    if let fallback = merged.first(where: { $0.id == stripped }) {
        selectedModel = fallback.id
        log("ShortcutSettings: dropped [1m] variant \(selectedModel) -> \(fallback.id)")
    }
} else if selectedModel.hasPrefix("gpt-"),
          let preferred = Self.preferredGptModel(in: merged, sameEffortAs: selectedModel) {
    // GPT model was filtered out (e.g. gpt-5.4/high after upgrading to 5.5).
    // Pick the same effort tier within the new generation, falling back
    // to the first GPT model if no effort match.
    selectedModel = preferred
    log("ShortcutSettings: upgraded GPT selectedModel \(normalizedSelection) -> \(preferred)")
} else if let upgraded = merged.first(where: { $0.id.contains(normalizedSelection) }) {
    selectedModel = upgraded.id
    log("ShortcutSettings: upgraded selectedModel \(normalizedSelection) -> \(upgraded.id)")
}

The rules in order: alias normalization (the Opus 4.7 case from April 22), bracket suffix stripping (the Sonnet 1M retirement case from April 30), same-effort GPT migration (the gpt-5.4 to gpt-5.5 case from May 1), and longest-prefix matching. The chain is short because most cases are one of the first three.

The same-effort GPT migration is the part worth slowing down on. The rule is in preferredGptModel(in:sameEffortAs:) at ShortcutSettings.swift:329. It is 20 lines. It splits the old id on "/" to get the effort tier (high, medium, low, xhigh), then searches the merged list in three passes: plain family at the requested effort (gpt-5.5/high is preferred over gpt-5.5-mini/high), then any model at the requested effort, then the first GPT model in the list. A user who had gpt-5.4/high pinned for two weeks lands on gpt-5.5/high the next time the picker repopulates.

// /Users/matthewdi/fazm/Desktop/Sources/FloatingControlBar/ShortcutSettings.swift
// Lines 329 to 348. The same-effort migration heuristic.

private static func preferredGptModel(in merged: [ModelOption],
                                      sameEffortAs oldId: String) -> String? {
    let effort = oldId.split(separator: "/").last.map(String.init) ?? "high"
    // Match base family (no variants like -mini, -codex) at requested effort first.
    let plainSameEffort = merged.first { opt in
        guard opt.id.hasPrefix("gpt-") else { return false }
        let parts = opt.id.split(separator: "/")
        guard parts.count == 2, parts[1] == effort else { return false }
        // Plain family = "gpt-X.Y" with no extra dashes after the version.
        let family = String(parts[0])
        let dashCount = family.filter { $0 == "-" }.count
        return dashCount == 1
    }
    if let plain = plainSameEffort { return plain.id }
    // Then any GPT at the requested effort.
    if let anySameEffort = merged.first(where: { $0.id.hasPrefix("gpt-") && $0.id.hasSuffix("/\(effort)") }) {
        return anySameEffort.id
    }
    // Last resort: first GPT model in the list.
    return merged.first(where: { $0.id.hasPrefix("gpt-") })?.id
}
0lines in isPickerEligible
0lines in preferredGptModel
0lines in the migration ladder
0hfrom April 30 to ship

What the picker does not do

The version floor is opinionated, not authoritarian. It is also not a model registry, a downloader, or a benchmark. The boundaries are worth being explicit about, because the alternative is a wishlist that maps poorly to what a single app on a single Mac can credibly do.

Boundaries of the rule

  • Does not download weights. Granite 4.1 and Qwen3.6 35B-A3B still need a local bridge (LM Studio, LiteLLM Anthropic-compatible mode, vllm) on localhost.
  • Does not auto-add Grok 4.3 to the picker. The picker shows what acp-bridge and codex-acp report; xAI is not in either, so Grok rides through the Custom API Endpoint field at SettingsPage.swift line 840 instead.
  • Does not benchmark anything. The version-floor rule is structural: gpt-5.5 and newer survive, everything older is hidden. Whether gpt-5.5 is actually better than gpt-5.4 on your workload is not a question the picker tries to answer.
  • Does not survive a user override. CodexBackendManager line 152 honors userVisibleModelIds first; the default frontier rule only fires when the user has not set a custom set. The picker is opinionated, not authoritarian.
  • Does not migrate across backend families. A user on 'sonnet[1m]' does not get auto-moved to a GPT id; the [1m] suffix is stripped and the user lands on 'sonnet'. Same family, narrower context.

Why this matters when a model lands tomorrow

The interesting property of the version-floor heuristic is that it generalises. When OpenAI ships gpt-5.6 next week, the existing binary will absorb it the next time codex-acp probes; the migration ladder will move users on gpt-5.5/high to gpt-5.6/high if a newer floor bump arrives. When Anthropic flips Opus 4.8 to GA, the alias normalization rule already in place (opus to default, normalize at read time) catches it. When a future Sonnet variant ships with a 2M context window beta, the bracket-stripping rule already handles the retirement.

The non-claim is that this picker rule is the source of truth on which model you should pick. It is not. The benchmarks people argue about (SWE-Bench Verified, MMLU, aider-polyglot, multi-step tool use) all still matter; the picker is just the bit that keeps the user interface honest when the upstream world reshuffles. For the April 29 to April 30, 2026 window, that meant five frontier events landed and the user on the floating bar saw their model pill continue to render the right label across all of them. The code that made that true is roughly 100 lines, distributed across two files.

Cross-references and source files

  • Frontier filter: /Users/matthewdi/fazm/Desktop/Sources/CodexBackendManager.swift lines 186 to 205, 206 lines total.
  • Migration ladder: /Users/matthewdi/fazm/Desktop/Sources/FloatingControlBar/ShortcutSettings.swift lines 211 to 348, 494 lines total.
  • CHANGELOG entries: /Users/matthewdi/fazm/CHANGELOG.json releases 2.6.2 through 2.7.2, dates 2026-04-28 to 2026-05-02.
  • Open source repository at github.com/m13v/fazm; download at fazm.ai/download.

Curious what your picker rebinds to on the next ship?

If you are running an AI agent on your Mac and want a walkthrough of how Fazm absorbs a model launch or retirement without an app update, book a 20-minute call. We will trace it on your actual setup.

Frequently asked questions

What models were actually released or retired between April 29 and April 30, 2026?

Five events in 48 hours. On April 29, IBM Research shipped Granite 4.1 30B under Apache 2.0 with a 131K context window and an AA Intelligence Index of 14.7. The same day, Alibaba pushed three Qwen3.6 variants: 35B-A3B (35B total parameters, 3B active per inference pass, runnable on consumer Apple Silicon), Qwen3.6 Plus, and Qwen3.6 Max Preview. On April 30, xAI released Grok 4.3. On April 30 as well, Anthropic retired the 1M-token context window beta for Claude Sonnet 4.5 and Sonnet 4. The interesting question for anyone running an AI agent on a Mac is not which model wins which benchmark. It is what the app on the user's machine does when those launches and retirements land in the same 48-hour window.

What part of Fazm decides which models show up in the picker the day a model ships?

Two short Swift functions. The first is isPickerEligible(modelId:) at /Users/matthewdi/fazm/Desktop/Sources/CodexBackendManager.swift, lines 190 to 205. It is 16 lines. It takes a Codex model id like 'gpt-5.5/high' or 'gpt-5.4-mini/low', strips the variant suffixes after the first hyphen ('-mini', '-codex'), parses the base 'gpt-X.Y', and returns true only when (major > 5) or (major == 5 and minor >= 5). The second is preferredGptModel(in:sameEffortAs:) at /Users/matthewdi/fazm/Desktop/Sources/FloatingControlBar/ShortcutSettings.swift, lines 329 to 348. It is 20 lines. It picks a replacement when a user's saved choice gets filtered out: prefer the plain family ('gpt-5.5/high' over 'gpt-5.5-mini/high') at the same effort tier; otherwise any model at that effort; otherwise the first GPT model in the merged list.

Why does it matter that isPickerEligible is structural and not benchmark-aware?

Because a benchmark-aware rule would require an app update every time a new model lands and would never agree across users. The version-floor approach lets a tiny Mac app absorb a Grok 4.3 or a Qwen3.6 launch with no App Store push: the picker rebuilds the next time the bridge reports its model list, the obsolete generation drops out, and the user keeps the same effort tier on the new generation through preferredGptModel(in:sameEffortAs:). The Fazm 2.7.1 changelog records the moment this went live: 'Streamlined GPT model picker to show only the latest generation (GPT-5.5)', dated 2026-05-01.

What happens when Anthropic retires the 1M-context Sonnet beta on April 30 and the user had sonnet[1m] saved?

The bracket annotation gets stripped at the migration ladder. Inside recomputeAvailableModels() at ShortcutSettings.swift lines 303 to 310, the code reads: 'else if selectedModel.contains("[1m]") { let stripped = normalizedSelection.replacingOccurrences(of: "[1m]", with: ""); if let fallback = merged.first(where: { $0.id == stripped }) { selectedModel = fallback.id; log("ShortcutSettings: dropped [1m] variant ...") } }'. The user lands on plain 'sonnet' the next time the picker repopulates. Fazm 2.7.1's accompanying changelog line covers the bridge half: 'Auto-fall-back to standard context when Claude rejects a request for the 1M-context tier'. Defense in depth on both sides of the wire.

Where exactly does Grok 4.3 fit in this picture, since it is not on acp-bridge or codex-acp?

Through the Custom API Endpoint field at /Users/matthewdi/fazm/Desktop/Sources/MainWindow/Pages/SettingsPage.swift line 840. The TextField is the literal Swift property '@AppStorage("customApiEndpoint") private var customApiEndpoint: String = ""'. It feeds the env dictionary the bridge subprocess inherits, via ACPBridge.swift lines 379 to 382 which set ANTHROPIC_BASE_URL when the value is non-empty. Point that at a local proxy that speaks Anthropic /v1/messages and translates to xAI, and the agent goes through Grok 4.3 with the same tool-use shape it expected from Claude. The picker still shows 'sonnet' or 'default' as the visible model; the wire-level routing is one TextField away.

What about Qwen3.6 35B-A3B and IBM Granite 4.1 - same path?

Same path. The agent expects Anthropic-shaped tool-use blocks regardless of which model serves them; the local bridge is responsible for translation. LM Studio in its Anthropic-compatible mode, LiteLLM with the appropriate provider config, the claude-anthropic-proxy family of small Go and TypeScript shims, and several model-specific projects all do this. Per-step input is small because Fazm sends a structured accessibility tree (typically two kilobytes for a Gmail inbox via mcp-server-macos-use), not a screenshot, so consumer hardware running Granite 4.1 or Qwen3.6 35B-A3B can keep up with the request rate the agent generates.

Why did Fazm ship four patches between April 28 and April 29, 2026 (2.6.2 through 2.6.6)?

Because the days around the April 29 to April 30 model window were also pop-out-chat-window stress days. CHANGELOG.json records the cadence: 2.6.2 on April 28 fixed duplicate pop-out chat windows, stale prior responses, and credit-exhausted session stuck states. 2.6.3 on April 29 fixed pop-out chat freezing under multiple-window streaming, an empty-AI-message error suffix, and a session-id eager persistence fix. 2.6.4 on April 29 fixed AI responses bleeding between chats after a timeout and Claude account re-auth flooding. 2.6.5 on April 29 fixed pop-out chat windows pairing the wrong question with the wrong response. 2.6.6 on April 29 added a contextual count to the New Chat Window button. None of those four patches modify the picker code path - the picker rule was already in place; the only thing 2.7.1 changed two days later was the visibility of the floor.

Is the user's selection allowed to override the frontier-only default?

Yes. CodexBackendManager.swift lines 138 to 174 wire a userVisibleModelIds set that is consulted before the static rule fires. Settings exposes a 'Visible Codex Models' editor (under Settings -> Advanced -> Codex Backend) where the user can toggle individual model ids on or off. setModelVisible(_:visible:) seeds the user set from the current eligible-by-default models on first use so flipping one switch does not accidentally hide everything else. resetVisibilityToDefault() drops the override and falls back to the static frontier rule. The point is that the version-floor heuristic is the default behavior, not a lock.

How does the agent itself find out a model changed under it without a restart?

restartBridgeForEndpointChange() at /Users/matthewdi/fazm/Desktop/Sources/Providers/ChatProvider.swift lines 2101 to 2107. Six lines. Reads the current customApiEndpoint from UserDefaults, logs the change, calls await acpBridge.stop(), and flips the internal acpBridgeStarted flag to false. The bridge is not restarted eagerly; it is restarted lazily on the next user query. So the new endpoint, the new ANTHROPIC_BASE_URL, and the new model list are picked up cleanly on the very next message, with no settings dialog interaction beyond the TextField onSubmit.

What was Fazm's actual ship between the model events and the picker rule going live?

Fazm 2.7.0 on April 30 (the same date as Grok 4.3 and the Sonnet 1M retirement) had nothing in CHANGELOG.json about models directly; the model-picker rule shipped one day later in 2.7.1 on May 1 alongside the Codex backend GA and the discovered-tasks rebuild. Three CHANGELOG entries in 2.7.1 together describe the response to the April 30 events: 'Streamlined GPT model picker to show only the latest generation (GPT-5.5)', 'Hide 1M-context model variants in built-in mode to prevent picking a tier without entitlement', and 'Auto-fall-back to standard context when Claude rejects a request for the 1M-context tier'. Roughly a 36-hour turnaround between the upstream event and the user-visible change. No App Store push was required for users who had auto-update on.

How did this page land for you?

React to reveal totals

Comments ()

Leave a comment to see what others are saying.

Public and anonymous. No signup.