Back to Blog
Published on

Google Just Deleted Your Roadmap: What the Assistant Shutdown Teaches Us About Renting Your Foundations

Software RescueSoftware ArchitectureAI StrategyEngineering LeadershipCloud & Infrastructure
Google Just Deleted Your Roadmap: What the Assistant Shutdown Teaches Us About Renting Your Foundations

Somewhere right now there's an engineering manager reading a deprecation notice and doing arithmetic in their head. Google Assistant is being retired from Android phones and tablets in favour of Gemini. For most people that's a consumer story. For a small number of product teams it's a forced migration with a date on it, landing in the middle of a quarter that was already fully committed.

The painful part isn't the migration itself. It's the discovery process. Someone has to open the codebase and answer a question nobody has asked in three years: where exactly do we touch this thing? Not "do we use Assistant" - of course you do, it's been in your integration surface for years. But which flows? Is it just the App Action that opens a screen? Or is it the voice-driven accessibility path that a subset of your users depend on and that never appears in your analytics because it doesn't fire the same events? Is it the smart-home linking that your hardware partner resells as a feature? Is it in the App Store screenshots?

Enumerating those touchpoints flow by flow - the archaeology, not the vendor list - usually takes a week to do properly. And that week comes out of the same sprint capacity you'd budgeted for the actual fix.

Deprecation isn't betrayal. It's weather.

Let's get the obvious take out of the way. Google didn't wrong anyone here. In our reading, Assistant had been strategically deprioritised for a while and a Gemini consolidation looked likely, and a company that keeps every product alive forever ends up with a portfolio nobody can maintain. Sunsetting is a sign of a healthy engineering organisation, not a hostile one.

The mistake isn't trusting a vendor. The mistake is treating an external platform as if it had the same lifecycle guarantees as your own code. Your code changes when you decide. A platform changes when someone else's OKRs change. Those are two completely different risk profiles, and most architectures don't distinguish between them at all.

Here's the uncomfortable version: if a third-party sunset can delete a quarter of your roadmap, that dependency was load-bearing and undeclared. The vendor's announcement didn't create the fragility. It just revealed it, on their schedule instead of yours.

The real cost is the archaeology, not the rewrite

When teams estimate a forced migration, they estimate the new integration. Wire up the new SDK, adapt the payloads, ship. Two weeks, maybe three.

What actually consumes the time:

Finding every touchpoint. Integrations spread. What starts as one module ends up referenced in a mobile app, a backend webhook handler, an ops runbook, a marketing page, a partner contract, and a Terraform file. There's rarely a single boundary to cut at.

Reconstructing intent. The person who built the integration has probably moved on. The behaviour is documented in the code, not the decisions. Why does the voice path skip the confirmation step? Was that a deliberate UX call or a workaround for a platform limitation that no longer exists? Nobody knows, so you either preserve a bug faithfully or risk breaking something you don't understand.

Renegotiating expectations. Someone sold this feature. It's in a deck, a renewal, an accessibility compliance statement. The technical migration is the easy conversation compared to telling a customer that a capability they were promised is changing shape.

Regression surface. In our experience, voice and assistant surfaces are hard to test automatically. You inherit a manual QA burden that competes with everything else in the release.

That's why, in our experience, a "three week" migration reliably becomes a quarter. And it's why platform deprecation belongs on the same list as the classic rescue triggers - recurring bugs, scary releases, blocked roadmaps, vendor opacity. The symptom looks external, but the underlying condition is internal: you don't have a map of your own system.

The dependency map is the cheapest artefact you'll ever produce

In our experience, most companies can't answer "which third-party platforms would break us if they disappeared tomorrow?" Not because they're careless, but because nobody owns the question. Procurement owns contracts. Engineering owns code. The gap between them is where load-bearing dependencies live undocumented.

You can close the vendor-level part of that gap in about 90 minutes with a whiteboard and a grep: which platforms you depend on, and roughly which surfaces they touch. That is a different job from the week of per-flow touchpoint archaeology above - the map doesn't eliminate the archaeology, it shortens it, because you start from a list of surfaces instead of a blank page. Do it before someone else's press release forces you to.

Start with a flat inventory. Every external platform your product touches at runtime, at build time, or in your compliance story. Then classify each one on three axes: how much of your product breaks if it vanishes (blast radius), how much work a replacement would be (swap cost), and how good the boundary around it is (abstraction).

# dependency-map.yaml — keep it in the repo, review it quarterly # status: RED | AMBER | GREEN | ACCEPTED — derived from the three axes below # RED = serious blast radius + weak or absent abstraction # AMBER = serious blast radius + partial abstraction, or minor radius + none # GREEN = clean boundary, swap is a diff # ACCEPTED = deliberately unabstracted, written down, owner on record dependencies: - name: Payment processor surface: [checkout, payouts, refunds] blast_radius: revenue-stops swap_cost: high # regulated, custom flows, stored tokens abstraction: partial # domain service exists, webhooks are not abstracted status: AMBER # revenue-stops + partial abstraction - name: LLM provider (chat + summarisation) surface: [assistant-panel, digest-emails] blast_radius: feature-degraded swap_cost: low abstraction: full # single adapter, prompts versioned, evals in CI status: GREEN - name: Voice / assistant platform surface: [android-app-actions, accessibility-path] blast_radius: feature-gone compliance_risk: accessibility-claim swap_cost: medium abstraction: none # SDK types leak into view models status: RED # feature-gone + no abstraction - name: Managed database (cloud provider) surface: [all-persistence] blast_radius: revenue-stops swap_cost: high abstraction: none # deliberate: we use provider-specific features status: ACCEPTED accepted: true rationale: >- A generic persistence layer buys portability we will never exercise and costs performance, features, and clarity. Reviewed 2026-Q3, platform team.

Red isn't "critical." Red is serious blast radius and a weak boundary. A payment processor deep in your revenue path can be green if you've kept a clean seam around it. A cosmetic feature can be red if its SDK types are smeared across forty files. The shape of the boundary determines your options; the blast radius determines the stakes; swap cost tells you what exercising those options will cost.

Three decisions come out of this, and only three:

Abstract it. Worth the effort when the dependency is replaceable and the boundary is thin. Put a seam in, own the domain model, treat the vendor as an implementation detail.

Accept it. Some dependencies aren't worth abstracting. If you're all-in on a cloud provider's managed database, a generic persistence layer costs you performance, features, and clarity in exchange for a portability you'll never exercise. Write down that you accepted the risk and why - that's what the ACCEPTED status and its rationale are for, so the entry reads as a decision rather than an oversight. That sentence is worth more than a fake abstraction.

Rip it out. If a dependency supports a feature nobody uses, deprecation is a gift. Delete the feature. This is the option teams reach for last and should reach for first.

The contrarian bit: abstracting everything is worse than abstracting nothing. A generic wrapper around a vendor you'll never swap adds indirection, hides behaviour, and creates a second thing to maintain. Portability has a price. Pay it deliberately, where the odds justify it.

What a real seam looks like

Since AI vendors are where this pressure is highest right now, use them as the worked example. We ship on OpenAI and Claude ourselves. The point isn't to avoid model providers - it's to make swapping one a two-day job instead of a two-quarter rebuild.

A seam is not wrapper.callTheApi(). A seam is a boundary expressed in your domain language, where the vendor's vocabulary stops existing.

// Your domain. No vendor nouns. No SDK types. No 'messages' array. export interface TranscriptSegment { speaker: string; startMs: number; text: string; } export interface Decision { id: string; statement: string; owner: string | null; } export interface ActionItem { id: string; description: string; owner: string | null; dueDate: string | null; } export interface SummariseInput { transcript: TranscriptSegment[]; audience: "exec" | "engineering"; } export interface MeetingSummary { headline: string; decisions: Decision[]; actions: ActionItem[]; confidence: "high" | "low"; } export interface MeetingSummariser { summarise(input: SummariseInput): Promise<MeetingSummary>; } // Domain failure modes. Vendor errors never escape the adapter. export class SummariserUnavailable extends Error {} export class SummariserInvalidOutput extends Error {}

Everything vendor-specific lives on the other side: prompt construction, token accounting, retries, tool schemas, JSON repair, model-specific quirks. Vendor failures stop there too - they come out as SummariserUnavailable or SummariserInvalidOutput, never as a 429 or a ZodError.

export class ClaudeSummariser implements MeetingSummariser { constructor(private client: Anthropic, private prompts: PromptRegistry) {} async summarise(input: SummariseInput): Promise<MeetingSummary> { const prompt = this.prompts.get("meeting.summary.v7"); let lastError: unknown; for (let attempt = 0; attempt < 3; attempt++) { try { const raw = await withTimeout( this.client.messages.create({ model: "claude-sonnet-4-5", max_tokens: 2000, system: prompt.system, messages: [{ role: "user", content: render(prompt.user, input) }], }), 20_000, ); // Vendor shape dies here. Nothing above sees this structure. return MeetingSummarySchema.parse(repairJson(extractJson(raw))); } catch (err) { lastError = err; // 429s, 5xx, 'overloaded', timeouts, and malformed JSON are all worth another go. if (attempt < 2 && isRetryable(err)) { await backoff(attempt); continue; } if (err instanceof ZodError) { throw new SummariserInvalidOutput("summary did not match the domain schema"); } throw new SummariserUnavailable("summariser call failed"); } } throw new SummariserUnavailable(`summariser exhausted retries: ${classify(lastError)}`); } }

Two details make this real rather than theatrical.

Prompts are versioned artefacts, not string literals. meeting.summary.v7 is a registry entry with a hash, an owner, and a changelog. In our experience, prompts are the highest-churn, least-reviewed code in an AI product. If they're inline strings, your migration involves grepping quoted paragraphs across a codebase.

Evals run in CI against the interface, not the vendor. This is the part teams skip, and it's the part that turns a swap from a gamble into a diff. Model output is nondeterministic, so a single assertion tells you nothing useful - score a fixture set across repeated runs and gate on a pass rate.

const providers = [ ["claude", makeClaude], ["openai", makeOpenAI], ] as const; const RUNS = 20; const PASS_RATE_THRESHOLD = 0.9; describe.each(providers)("MeetingSummariser: %s", (_name, make) => { it.each(fixtures.decisionCases)("extracts decisions: $id", async (fixture) => { const summariser = make(); let passes = 0; for (let run = 0; run < RUNS; run++) { const result = await summariser.summarise(fixture.input); const ids = result.decisions.map(d => d.id); const ok = fixture.expectedDecisionIds.every(id => ids.includes(id)) && result.confidence === fixture.expectedConfidence; if (ok) passes++; } expect(passes / RUNS).toBeGreaterThanOrEqual(PASS_RATE_THRESHOLD); }); });

Run the same suite against every candidate provider. Now "should we move off this model?" has an answer with numbers instead of vibes: pass rates per fixture, per provider, in the CI log. Without evals you can build a perfect abstraction and still be unable to switch, because nobody can prove the new provider is as good as the old one. That's the trap: the interface is clean, and the decision is still stuck. Anyone who's watched a large voice assistant stall mid-rebuild has seen this pattern from the outside.

And keep a kill switch. Config-level, not code-level. The composition root is the one place in the system where vendor names are allowed to exist - application code below it never learns them.

// Resolved per call, so a config change doesn't need a code change. // The id is just a registered string ("claude", "openai", "local"); nothing // outside this function and the registry's boot-time wiring sees it. function getSummariser(): MeetingSummariser { return providerRegistry.resolve(config.current().providers.summariser); }

If a provider degrades on a Friday afternoon, you change an environment variable and restart the service. If your config source supports hot reload, the next call picks up the change instead. Either way, you don't ship a hotfix.

The rule: assume an expiry date you don't control

Every external dependency has a sunset. You don't know the date, and you don't get a vote. That's not cynicism, it's just how someone else's roadmap works.

The architectural response isn't paranoia or in-housing everything. It's humility, written into the design. Own your domain model. Let vendors be implementations. Keep a dependency map that a non-engineer can read. Write down which risks you've accepted, so the next person doesn't mistake a decision for an oversight. And build the tests that let you prove a swap is safe, because portability you can't verify isn't portability.

That's the thesis, and this deprecation illustrates it precisely. The cost of a forced migration is set by three things: whether the dependency was on a map, how much of the vendor's vocabulary leaked into your domain model, and whether you can prove a replacement behaves. A perfectly stable codebase with Assistant types smeared through its view models still pays twice - once for the original integration, once to rebuild it. A messier codebase with a clean seam and a passing eval suite pays once.

If you can't currently answer "which third-party platforms would break us if they disappeared?" - that's the finding. Not a crisis, just a gap with a known cost. It's cheaper to close it on a quiet Tuesday than during someone else's migration window.

Platform deprecation is the fifth rescue trigger, alongside recurring bugs, scary releases, blocked roadmaps, and vendor opacity. If you've just realised you can't answer the dependency question, let's spend thirty minutes on it.

Book a Free Rescue Call

Related articles

Your AI Agent Didn't Read the Handbook: Why Governance Has to Be Code, Not Prose
AI Strategy

Your AI Agent Didn't Read the Handbook: Why Governance Has to Be Code, Not Prose

Research confirms long policy prompts don't reliably govern AI agents. Real governance lives in tool permissions, validators and state machines - not English.

The $314 Billion AI Bubble: Why Silicon Valley Needs You to Believe (And Why Most Companies Still Need Humans)
AI Strategy

The $314 Billion AI Bubble: Why Silicon Valley Needs You to Believe (And Why Most Companies Still Need Humans)

Silicon Valley has bet $314B on AI being the future. But the math-context windows, human reviewers, and computational costs-tells a different story. Here's why stabilizing your systems first isn't just smart engineering, it's the only path that works.

Why 'AI Slop' Is the Red Flag Your Software Rescue Needs Now
AI Strategy

Why 'AI Slop' Is the Red Flag Your Software Rescue Needs Now

The creator economy is drowning in AI-generated garbage. If your platform is rushing to add AI features on top of unstable foundations, you're not innovating-you're compounding technical debt that will cost you twice to fix.