<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Elisheba Builds on Medium]]></title>
        <description><![CDATA[Stories by Elisheba Builds on Medium]]></description>
        <link>https://medium.com/@elisheba.t.anderson?source=rss-7f60e4e112c5------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*onmZ1eKZfzd8cjxP7riIxQ.jpeg</url>
            <title>Stories by Elisheba Builds on Medium</title>
            <link>https://medium.com/@elisheba.t.anderson?source=rss-7f60e4e112c5------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 18:06:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@elisheba.t.anderson/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[The Model Proposes, Deterministic Code Disposes]]></title>
            <link>https://medium.com/@elisheba.t.anderson/the-model-proposes-deterministic-code-disposes-3d2aae1b5e66?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/3d2aae1b5e66</guid>
            <category><![CDATA[rules]]></category>
            <category><![CDATA[mcps]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[compliance]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 15:26:01 GMT</pubDate>
            <atom:updated>2026-11-10T15:26:01.885Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Async buffers, strict boundaries, and where I keep LLMs out of the execution path</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*LF79sUgOD5ozkqTOW_7bIg.jpeg" /></figure><p>Every AI workflow that touches a system of record has the same shape: something unreliable sits next to something that can’t be wrong. In my case, the system of record was a payments platform, and the unreliable thing changed over time. First it was third-party vendors (Plaid and Giact), which are rate-limited. Later it was the model itself, calling tools I had exposed over MCP.</p><p>I worked on both: an event-driven verification pipeline on a platform that moved over $1.1B in transaction volume, and later MCP tool servers for autonomous workflows. The fixes turned out to be one idea applied at different layers. Put a boundary between the unreliable thing and the system that has to be right.</p><p>This post follows a single request through that architecture, shows what broke at each stage in v1, and ends with where I draw the line on using LLMs at all.</p><h3>The shape of the system</h3><p>A request crosses four boundaries. Each one exists because something specific broke without it:</p><ol><li><strong>Buffer.</strong> SQS decouples intake from vendor latency.</li><li><strong>Protect.</strong> AWS KMS envelope encryption keeps plaintext PII out of storage and logs.</li><li><strong>Validate.</strong> Pydantic schemas and deterministic normalization sit at every tool edge.</li><li><strong>Recover.</strong> Retries and Sentry Alerts mean no failure is silent.</li></ol><h3>1. Buffer: don’t let a vendor set your latency</h3><p>My v1 verification flow called vendors synchronously inside the request cycle. When a vendor hit a latency spike or returned a 504, the caller hung, and some transactions were blocked.</p><p>An agent as the caller makes this worse. A 15- to 30-second vendor stall times out the orchestrator turn, the agent retries, and now you are hammering a rate-limited endpoint and collecting 429s on top of the 504s.</p><p>The fix was to stop doing vendor work on the request path. API Gateway accepts the request and writes it straight to SQS. Lambda workers pick it up and absorb the vendor latency on their own time. Bursts queue up instead of running into vendor rate limits, and the caller never waits on a third party.</p><h3>2. Protect: no plaintext past the front door</h3><p>Identity and account records moving through an orchestration pipeline are a liability. Anywhere they pass, plaintext PII can end up in logs and state stores.</p><p>So sensitive fields are encrypted with AWS KMS envelope encryption before anything is persisted to DynamoDB. Everything downstream works with encrypted tokens, which is how the pipeline kept a record of zero plaintext PII leaks.</p><h3>3. Validate: strict where it matters, forgiving where it should be</h3><p>Validation failed me in two opposite directions.</p><p><strong>Too loose.</strong> When an LLM calls a backend tool over MCP, it can send malformed JSON or invent an enum value, and either can crash a downstream service. At the MCP boundary (FastAPI, Pydantic, Cloud Run), every payload is validated before anything executes. A bad call gets a structured validation error back, so the model can correct itself instead of corrupting state.</p><p><strong>Too rigid.</strong> In the identity pipeline, exact string matching rejected legitimate users with compound surnames, hyphens, and diacritics. Each false decline became a ticket for a human CX agent. The fix was a deterministic normalization layer that runs before vendor dispatch.</p><p>These are the same boundary doing two jobs. Here is the compact version:</p><pre>import unicodedata<br>from typing import Literal<br>from pydantic import BaseModel, Field, field_validator<br><br>class VerificationToolPayload(BaseModel):<br>    &quot;&quot;&quot;Strict MCP tool boundary: rejects invented args, normalizes names.&quot;&quot;&quot;<br>    account_id: str = Field(..., pattern=r&quot;^ACC-[0-9]{6,12}$&quot;)<br>    kms_encrypted_pii_token: str = Field(..., min_length=32)<br>    applicant_surname: str = Field(..., min_length=1, max_length=120)<br>    workflow_action: Literal[&quot;VERIFY_IDENTITY&quot;, &quot;ESCALATE_TO_CX&quot;]<br><br>    @field_validator(&quot;applicant_surname&quot;)<br>    @classmethod<br>    def normalize_surname(cls, raw: str) -&gt; str:<br>        # Strip diacritics, split compound surnames, uppercase.<br>        decomposed = unicodedata.normalize(&quot;NFKD&quot;, raw)<br>        stripped = &quot;&quot;.join(c for c in decomposed if not unicodedata.combining(c))<br>        return &quot; &quot;.join(stripped.upper().replace(&quot;-&quot;, &quot; &quot;).split())</pre><p>Two properties matter here. Anything outside the contract is rejected, not repaired. And normalization is a pure function, so the same input always produces the same output, which keeps every decision auditable.</p><h3>4. Recover: make failure visible and replayable</h3><p>Workers retry failed vendor calls. Persistent failures route to Sentry alerts and an internal investigation tool for CX agents.</p><p>Nothing is lost, because every failed payload is preserved for replay. Nothing is silent either, because each failure surfaces with a structured reason. Support no longer has to guess what happened to a transaction that timed out mid-flight.</p><p>Across production, this architecture covered 92% of active accounts, reduced fraud by 20%, and kept zero plaintext PII leaks. I handed it off with Sentry runbooks and CX training guides.</p><h3>Where I use LLMs, and where I don’t</h3><p>The boundaries above are what make it safe to use LLMs anywhere near this system. The models live outside the boundary, never inside it.</p><p><strong>Where they earn their place:</strong></p><ul><li>Summarizing unstructured context.</li><li>Routing between fast, lightweight models and high-reasoning ones such as Claude Sonnet or Gemini Flash.</li><li>Auditing quality with a 20% sample-rate LLM-as-a-judge pipeline, traced in Langfuse and run asynchronously to track accuracy and token ROI over time.</li></ul><p><strong>Where I rejected them entirely:</strong> identity verification, sanctions screening, and PII handling. Those need execution that is strict, auditable, and incapable of hallucinating. An LLM can orchestrate the outer workflow over MCP, but the execution boundary has to stay 100% deterministic.</p><h3>Takeaways</h3><ul><li>Never tie a user-facing or agent-facing turn to a vendor’s latency. Buffer it.</li><li>Validate at every tool edge. Reject what’s invalid, and normalize what’s merely messy.</li><li>Models can propose actions. Deterministic code decides whether they run.</li></ul><p>What is your take?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d2aae1b5e66" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I’d Let 5,000 Engineers Use LLMs Without Getting a Bank Fined]]></title>
            <link>https://medium.com/@elisheba.t.anderson/how-id-let-5-000-engineers-use-llms-without-getting-a-bank-fined-097bf0c476e1?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/097bf0c476e1</guid>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[fintech]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Tue, 27 Oct 2026 14:31:01 GMT</pubDate>
            <atom:updated>2026-10-27T14:31:01.622Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Building a governed AI platform that helps engineers move faster without sacrificing security, risk management, or production reliability.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*FFJLdSQ9gQAoHZeXhFJQQg.png" /></figure><p>Many companies want their engineers to move faster with AI.</p><p>The challenge is making that possible without creating thousands of different security, data protection, and risk-control implementations.</p><p>One team integrates a model API. Another builds a RAG pipeline over internal documents. A third adds an AI coding assistant.</p><p>Each team makes its own decisions about authentication, logging, PII handling, model selection, and evaluation.</p><p>That works for a handful of experiments. It becomes much harder to manage when AI adoption spreads across an enterprise.</p><p>The problem isn’t a lack of promising use cases. It’s getting those use cases into production consistently, with controls that scale alongside them.</p><p>AWS explored this challenge in its article, <a href="https://aws.amazon.com/blogs/industries/building-an-ai-stack-for-banking-on-aws/">Building an AI Stack for Banking on AWS</a>. The core message is that moving from proof of concept to production requires more than choosing a model. It requires reusable technology, governance, evaluation, monitoring, and an operating model that supports delivery at scale.</p><p>My proposal follows that direction:</p><p><strong>Build a shared AI platform that makes the secure, governed path the easiest path for engineers to use.</strong></p><p>The goal isn’t to centralize every AI application or force every team to use the same model.</p><p>It’s to centralize the controls that should be consistent across the organization while letting teams build and deploy their own solutions.</p><h3>1. Start With a Shared AI Gateway</h3><p>The first component I’d build is a gateway that provides a consistent interface to approved models and inference services.</p><p>Instead of every application integrating directly with a model provider, applications call the gateway.</p><p>The gateway handles common platform responsibilities:</p><ul><li>Authentication and authorization</li><li>Model routing and approved-provider policies</li><li>Usage limits, quotas, and cost attribution</li><li>Consistent request and response handling</li><li>Centralized telemetry and policy-decision records</li><li>Enforcement of applicable input and output controls</li></ul><p>This isn’t a new architectural idea. Organizations already use centralized AI gateways in production to simplify access to multiple models, standardize integrations, and manage usage.</p><p>AWS also identifies gateways, model evaluation, guardrails, and model monitoring as foundational components of an enterprise AI stack.</p><p>What I’d propose is making the gateway the standard entry point for internal AI applications, with documented exceptions for workloads that need a different integration pattern.</p><p>The implementation could use an existing gateway product or a custom service. LangChain could support application orchestration, while MCP could standardize how applications interact with tools and resources.</p><p>Neither should be treated as a substitute for the gateway’s security and policy-enforcement layer.</p><p><strong>The important decision is the architecture, not the framework.</strong></p><h3>2. Separate Inference From Risk Controls</h3><p>I’d avoid making the model responsible for deciding whether its own output is safe to use.</p><p>Instead, I’d separate four responsibilities:</p><p><strong>Inference:</strong> Generates the model response.</p><p><strong>Guardrails and policy enforcement:</strong> Applies the relevant input and output rules.</p><p><strong>Evaluation:</strong> Measures quality, safety, and task-specific performance.</p><p><strong>Monitoring:</strong> Detects changes in production behavior and operational health.</p><p>These components should work together without being tightly coupled to a particular model.</p><p>Imagine an engineering team changing its code-review assistant from one model to another.</p><p>The gateway can route requests to the new model, but the platform should still run regression tests, compare performance, and verify that existing controls continue to work.</p><p>A model change shouldn’t automatically mean rebuilding every application.</p><p>It also shouldn’t mean assuming previously approved behavior will remain unchanged.</p><p>I’d version policies, evaluation suites, prompts, and model configurations independently wherever practical. Changes that affect risk should trigger the appropriate review and approval process.</p><p>This is the balance I’m aiming for: reusable controls, faster model upgrades, and explicit validation when something material changes.</p><h3>3. Validate Requests Before They Reach a Model</h3><p>A shared gateway gives the organization a consistent place to enforce common rules.</p><p>Consider an internal application that summarizes a customer’s dispute history.</p><p>I’d design the request path roughly as follows.</p><p><strong>Step 1: Authenticate the caller.</strong></p><p>Establish which application and user are making the request, and whether they are authorized to access the relevant information.</p><p><strong>Step 2: Validate the request.</strong></p><p>Check required fields, types, sizes, and allowed operations against a defined schema.</p><p><strong>Step 3: Apply data controls.</strong></p><p>Detect sensitive information, enforce data-handling rules, and redact or block information where required by policy.</p><p><strong>Step 4: Check the intended use.</strong></p><p>Apply the permissions and restrictions associated with the application, model, and data classification.</p><p><strong>Step 5: Route to an approved model.</strong></p><p>Send the request only to an endpoint authorized for that workload.</p><p><strong>Step 6: Validate the response.</strong></p><p>Check output structure, apply relevant safety and data-loss controls, and reject responses that don’t meet the application’s requirements.</p><p><strong>Step 7: Record the decision.</strong></p><p>Capture the request ID, model and configuration versions, applicable policy versions, evaluation signals, and relevant outcomes.</p><p>The exact checks depend on the use case. A coding assistant, an internal knowledge search, and an application that processes customer records won’t have identical requirements.</p><p>Schema validation doesn’t prove that a request is safe. PII detection can miss information, and a structurally valid response can still be incorrect or disclose sensitive data.</p><p>The controls need to be layered and tested against realistic failure scenarios.</p><p>I’d also be careful about what gets logged.</p><p>A centralized audit trail is valuable, but retaining every prompt and response can create another sensitive-data store. Access controls, encryption, retention policies, redaction, and tamper-evident records should be designed into the logging system.</p><p>The objective is to make enforcement consistent and evidence easier to retrieve, not to claim that one gateway eliminates risk.</p><h3>4. Make Retrieval Respect Data Governance</h3><p>RAG introduces another failure mode: an application can generate a plausible answer using the wrong document.</p><p>Imagine a lending-policy assistant that retrieves a superseded policy because it has a high similarity score to the user’s question.</p><p>The answer might be well written and still be wrong.</p><p>I’d address this at the retrieval layer rather than relying on the model to recognize outdated information.</p><p>For documents stored in Amazon S3, I’d maintain metadata such as:</p><ul><li>Document version and effective date</li><li>Current or retired status</li><li>Owning department</li><li>Data classification</li><li>Applicable business unit or jurisdiction</li><li>Access-control attributes</li></ul><p>The retrieval pipeline should apply authorization and document-status filters <strong>before</strong> ranking eligible documents by similarity.</p><p>A retired policy shouldn’t be eligible for a query asking for the currently applicable policy. A user shouldn’t be able to retrieve a restricted document simply because its contents match the question.</p><p>I’d also track document ingestion, metadata quality, index freshness, and changes to the source of truth.</p><p>Metadata filters are only as reliable as the information they depend on.</p><p>For auditability, the application should retain enough provenance to establish which document versions informed a response, subject to the organization’s data-retention requirements.</p><p>This is a reusable platform capability. Teams building internal search, policy assistants, and document summarization shouldn’t each have to reinvent it.</p><h3>5. Evaluate Before Deployment, Not Just After Something Breaks</h3><p>A gateway helps standardize access, but it doesn’t establish that an AI application is fit for production.</p><p>I’d make evaluation a standard part of the deployment process.</p><p>Each application should have tests appropriate to its intended use, including:</p><ul><li>Task accuracy and answer quality</li><li>Retrieval relevance and source attribution</li><li>PII handling and unauthorized disclosure</li><li>Prompt-injection and jailbreak resistance</li><li>Output-schema compliance</li><li>Latency and cost under expected workloads</li><li>Performance against known failure cases</li></ul><p>Some checks can be automated with conventional tests or model-based evaluators. Others may require human review, particularly when the application affects customers, material business decisions, or regulated processes.</p><p>I’d establish minimum acceptance criteria before deployment, then run the relevant tests whenever a model, prompt, retrieval configuration, or policy changes.</p><p>The same evaluation framework should be available to developers during experimentation and in the release pipeline.</p><p>This creates a repeatable process.</p><p>Teams can compare approaches, identify regressions, and demonstrate why a configuration was approved, rather than relying on a successful demo as evidence of production readiness.</p><h3>6. Treat Silent Failures as a First-Class Operational Risk</h3><p>Traditional infrastructure monitoring tells us whether a service is available.</p><p>AI applications need additional signals to tell us whether the service is still doing the right thing.</p><p>A model can return HTTP 200 responses while producing worse answers. A retrieval index can become stale without causing an outage. A prompt change can increase unsupported claims while latency remains unchanged.</p><p>I’d monitor four categories.</p><h3>Quality and safety</h3><p>Track evaluation results, failure rates, source-grounding signals, and sampled outputs against known-good cases.</p><p>Define thresholds that trigger investigation or a rollback.</p><h3>Retrieval and data freshness</h3><p>Monitor ingestion failures, index lag, document versions, and the freshness of the sources being retrieved.</p><h3>Policy and schema behavior</h3><p>Track blocked requests, validation failures, and changes in policy-trigger rates.</p><p>A sudden increase or an unexpected drop can indicate a regression or a change in traffic.</p><h3>Model and configuration drift</h3><p>Record which model, prompt, retrieval configuration, and policy versions are active.</p><p>Compare production changes with the configurations that passed approval.</p><p>These signals should feed operational dashboards, alerts, incident response, and periodic reviews.</p><p>I’d also define what happens when a check fails.</p><p>Depending on the application, the response might be to reject a request, fall back to an approved model, require human review, or disable a feature until the problem is resolved.</p><p>The right response depends on the business risk. The important thing is that the behavior is designed in advance rather than improvised during an incident.</p><h3>7. Put Governance Around the Platform and Its Applications</h3><p>Technical controls are only part of the problem.</p><p>A bank also needs to know which AI applications are in production, what they are used for, who owns them, and which risks and approvals apply.</p><p>I’d establish a central registry for production AI use cases and their associated models and configurations.</p><p>For each application, I’d want to capture:</p><ul><li>Business purpose and accountable owner</li><li>Data sources and data classifications</li><li>Model and configuration versions</li><li>Risk classification and required controls</li><li>Evaluation results and approval history</li><li>Deployment status and monitoring arrangements</li><li>Relevant dependencies and change history</li></ul><p>I’d pair that registry with a defined approval process.</p><p>Not every internal assistant needs the same review as a customer-facing application or a model used in a material credit decision.</p><p>The process should reflect the use case, applicable regulatory obligations, and the organization’s risk framework.</p><p>For a bank, this may involve model risk management, information security, privacy, compliance, legal, and business stakeholders.</p><p>Requirements associated with OCC supervision, SEC rules, recordkeeping, and other obligations must be assessed according to the activities and entities involved.</p><p>A gateway and an audit trail can help demonstrate that controls exist and operate as intended.</p><p><strong>They don’t, by themselves, establish regulatory compliance.</strong></p><p>I’d treat governance as part of the deployment lifecycle rather than a separate process that begins after engineering is finished.</p><h3>8. Make the Platform a Product, Not a Ticket Queue</h3><p>A centralized platform can become a bottleneck if every model, prompt, and application change requires a manual request to a small central team.</p><p>I’d avoid that by providing developers with reusable building blocks:</p><ul><li>Standard SDKs and APIs</li><li>Approved model configurations</li><li>Reference implementations for common use cases</li><li>Reusable RAG and tool-integration patterns</li><li>Evaluation templates and CI/CD integration</li><li>Self-service usage reporting and cost attribution</li><li>Clear documentation and escalation paths</li></ul><p>The central team would own shared infrastructure, platform-level controls, and enterprise standards.</p><p>Application teams would own their use cases, application-specific evaluations, and day-to-day behavior within those standards.</p><p>A federated model is likely to work better than insisting that one team build every AI application. Larger business units can retain specialized capabilities while following common platform requirements.</p><p>I’d start with a small number of valuable use cases, establish the minimum reusable components needed to support them, and improve the platform as practical experience exposes gaps.</p><p>That is more useful than spending months trying to design every possible control before delivering anything.</p><h3>9. Measure the Platform by More Than Model Usage</h3><p>The number of model calls or onboarded engineers doesn’t tell us whether the investment is delivering value.</p><p>I’d track:</p><ul><li>Time required to move a use case from prototype to production</li><li>Reuse of platform components across teams</li><li>Evaluation pass rates and production regressions</li><li>Security and policy incidents</li><li>Model costs by team and use case</li><li>Latency and reliability</li><li>Business outcomes for the applications being deployed</li></ul><p>Cost attribution is particularly important at scale.</p><p>Engineers need enough visibility to compare models and understand the cost of their workloads, while platform owners need the information to manage capacity, negotiate provider arrangements, and identify waste.</p><p>The aim is to make speed, reliability, and cost visible together.</p><h3>What I’d Propose and What Production Experience Already Tells Us</h3><p>The individual building blocks in this design are not speculative.</p><p>Centralized model gateways, reusable AI components, evaluation, guardrails, monitoring, and governed deployment pipelines are established enterprise AI patterns.</p><p>Publicly documented implementations also show organizations using gateways to simplify access to multiple models and reduce duplicated integration work.</p><p>AWS’s banking guidance makes a similar case for reusable foundational components, evaluation, guardrails, monitoring, model registries, and a production lifecycle built around governance and control.</p><p>What I’m proposing is to combine those patterns into a coherent engineering platform for a regulated environment.</p><p>I would not claim that a single gateway guarantees compliance, prevents every disclosure, or removes the need for model validation. Nor would I assume that every bank needs the same organizational structure or the same set of controls.</p><p>The design should be adapted to the organization’s risk profile, existing architecture, regulatory obligations, and maturity.</p><p>But the direction is clear: establish common foundations, make them easy to consume, and build evaluation, governance, and monitoring into the production lifecycle.</p><p>The banks that scale AI effectively won’t necessarily be the ones with the most restrictive approval processes.</p><p>They’ll be the ones that make it possible for engineers to build quickly while giving risk, security, and compliance teams the evidence and control they need.</p><p><strong>That’s the platform I’d propose: centralized where consistency matters, federated where teams need flexibility, and governed throughout the lifecycle.</strong></p><p>If you’re building an AI platform in a regulated environment, I’d be interested in comparing approaches, especially around balancing developer self-service with model governance, data protection, and production reliability.</p><p><em>Further reading: </em><a href="https://aws.amazon.com/blogs/industries/building-an-ai-stack-for-banking-on-aws/"><em>Building an AI Stack for Banking on AWS</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=097bf0c476e1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Metadata Is the Difference Between Retrieval and Guessing]]></title>
            <link>https://medium.com/@elisheba.t.anderson/metadata-is-the-difference-between-retrieval-and-guessing-07869b1b104a?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/07869b1b104a</guid>
            <category><![CDATA[metadata-management]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[object-storage]]></category>
            <category><![CDATA[metadata]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Tue, 06 Oct 2026 16:16:28 GMT</pubDate>
            <atom:updated>2026-10-06T16:16:28.878Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/616/1*EWukOELNgo5OqkiZ8DkIHQ.jpeg" /></figure><p>Similarity search can’t tell an outdated guideline from the current one. Both look relevant. Metadata is what tells them apart.</p><p>Let’s look at an example: In a clinical agent, a plausible answer from the wrong protocol version is worse than no answer.</p><h3>The problem</h3><p>Clinical knowledge can be versioned, specialty-specific, and sometimes contradictory. Patient data changes by the hour. If you dump both into a vector store and hope embeddings sort it out, you’ll get confident answers on stale evidence.</p><h3>What I learned about S3 metadata</h3><p>I picked this up in the AWS Gen AI learning plan, and it changed how I think about ingestion. Your S3 bucket is the foundation for any knowledge base or anything feeding a foundation model, and its structure shapes retrieval quality before a single embedding is created. Sure, you can save files without metadata, but you need the metadata.</p><p><strong>System-generated metadata:</strong> S3 records content type, size, last-modified date, and storage class automatically. It’s free, and it’s useful for freshness checks and auditing.</p><p><strong>User-generated metadata:</strong> This is where the value is. Custom key-value pairs describe what the document is: protocol_version, effective_date, specialty, status (active or superseded), source_authority. Object tags add another layer for access control and lifecycle rules.</p><h3>Tactics that improve retrieval</h3><ul><li><strong>Hierarchical prefixes.</strong> Organize by specialty, then protocol, then version. Structure carries meaning before any query runs.</li><li><strong>Sidecar metadata for knowledge bases.</strong> Amazon Bedrock Knowledge Bases can filter on metadata attributes you attach to each document, so you narrow the candidate set before similarity search.</li><li><strong>Filter first, then search.</strong> Restrict to active protocols for the right specialty, then rank by relevance. This fixes the wrong-version problem.</li><li><strong>Enrich at ingestion.</strong> Extract metadata with rules or an LLM when documents arrive, and validate it. Bad metadata is worse than none.</li><li><strong>Retire, don’t delete.</strong> Mark superseded protocols as inactive so they stay auditable but never get retrieved.</li><li><strong>Query your metadata.</strong> S3’s metadata tooling lets you inspect what you have, which surfaces gaps like documents with no effective date.</li></ul><h3>Keep live patient data out of the index</h3><p>Patient context shouldn’t be embedded. It goes stale and creates compliance exposure. Fetch it through tool calls at query time and combine it with retrieved protocol content when reasoning. The index holds knowledge, and the tools hold state.</p><h3>Where I’m coming from</h3><p>I haven’t shipped a clinical agent, and I won’t pretend otherwise. What I have built is close to this pattern. At Amivero, I designed a serverless RAG platform on Bedrock with Titan embeddings and human-in-the-loop evaluation, which cut document review cycles by 88%. I also built an OCR pipeline with state-aware validation and metadata enrichment, which is the same idea as specialty- and version-aware retrieval.</p><h3>How I’d approach it</h3><ol><li>Define the metadata schema with clinicians before ingesting anything.</li><li>Filter on version, date, and specialty before similarity search.</li><li>Require citations, and have the agent abstain when retrieval confidence is low.</li><li>Evaluate retrieval separately from generation, so you know which one failed.</li></ol><p>Better prompts won’t fix bad retrieval. Better data structure will, and that starts in the bucket.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=07869b1b104a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My motto: Trace First, Eval Second: Building Evals for AI Agents]]></title>
            <link>https://medium.com/@elisheba.t.anderson/my-motto-trace-first-eval-second-building-evals-for-ai-agents-7bc6a76386c5?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/7bc6a76386c5</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[mlops]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Tue, 29 Sep 2026 16:16:01 GMT</pubDate>
            <atom:updated>2026-09-29T16:16:01.908Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*L0zXKoWvYdJoGMe04ICVAg.jpeg" /></figure><p><strong>The bug wasn’t the model.</strong> Some “AI eval” content stops at unit tests for prompts. Mine started somewhere messier: a fitness coaching agent that occasionally counted my push-ups wrong, and a career-tracking tool that sometimes forgot the one number it was supposed to report.</p><p>Across two projects, <a href="https://github.com/ElishebaW/chaosfit">Chaos Fit</a>, a real-time AI workout coach, and <a href="https://massavelabs.com/impact-tracker">Global Impact Tracker</a>, an MCP server that turns engineering work into performance-review narratives, I built eval systems that do one thing well: separate “the model got creative” from “the code has a bug.”</p><p><strong>That distinction matters more than it sounds.</strong> In Chaos Fit, some failures were genuinely non-deterministic. The vision model flagging a camera adjustment as a rep, or the coaching agent repeating the same cue three times instead of noticing a new error. Those get caught with dataset-driven evals: 27 cases pulled straight from real session traces (<a href="https://github.com/ElishebaW/chaosfit/blob/main/evals/dataset.json">evals/dataset.json</a>), scored against rules in <a href="https://github.com/ElishebaW/chaosfit/blob/main/evals/evaluators.py">evals/evaluators.py</a> like a zero-tolerance threshold for phantom reps (eval_rep_count_accuracy) and a ban on generic praise (eval_correction_specificity) — &quot;good job&quot; doesn&#39;t count as coaching.</p><p>Other failures like interruption counts silently merging with correction counts (documented as F-1 in the project’s <a href="https://github.com/ElishebaW/chaosfit/blob/main/specs/2026-05-18-phase-1-coaching-accuracy/failure_taxonomy.md">failure_taxonomy.md</a>) were deterministic state-management bugs in session_manager.py. No amount of re-prompting fixes those; they needed an invariant check in the <a href="https://github.com/ElishebaW/chaosfit/blob/main/test/trace_harness.py">trace harness</a> and a code fix in session state, not an eval.</p><p><strong>The same philosophy, a different failure surface.</strong> Global Impact Tracker generates LLM narratives. Before a story ships, a deterministic check (_missing_metrics, in server.py) confirms the exact metrics, hours saved, latency reduction, project count, actually appear in the text. If they don&#39;t, the model gets a targeted critique and regenerates. A separate LLM-as-judge (evaluate_star_story, in server_utils.py) scores narrative quality on a 1–5 rubric, and because judges are non-deterministic too, _parse_judge_response clamps its output range and falls back to a local model if the primary judge fails or rate-limits. This only runs on 20% of responses for performance.</p><p><strong>Trace first, eval second.</strong> As the project spec puts it: <em>“no eval is written before trace data exists to justify it; observed failures define the dataset.”</em> That’s produced a small, concrete taxonomy of reproducible issues instead of a vague “make it better” backlog — and it’s let me build regression gates that catch the same bug twice.</p><p>If you’re curious about evaluation methodology, agent tracing, or reliability engineering for LLM systems, let’s chat!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7bc6a76386c5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to cut AI costs with Model Routing]]></title>
            <link>https://medium.com/@elisheba.t.anderson/how-to-cut-ai-costs-with-model-routing-b454adce54f7?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/b454adce54f7</guid>
            <category><![CDATA[ai-cost-optimization]]></category>
            <category><![CDATA[gemini]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[hugging-face]]></category>
            <category><![CDATA[model-routing]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Tue, 15 Sep 2026 16:01:02 GMT</pubDate>
            <atom:updated>2026-09-15T16:01:02.635Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/548/1*4QZNvmnewypzX6XZ3N8dpg.jpeg" /><figcaption>Overpass of intersecting freeway by Rosley Majid from &lt;a href=”<a href="https://thenounproject.com/photo/overpass-of-intersecting-freeway-4dy9rb/">https://thenounproject.com/photo/overpass-of-intersecting-freeway-4dy9rb/</a>&quot; target=”_blank” title=”Overpass of intersecting freeway Photo”&gt;Noun Project&lt;/a&gt; (CC BY-NC-ND 2.0)</figcaption></figure><p><strong>TL;DR:</strong> Default-routing <em>every single user prompt</em> to flagship frontier models is one of the fastest ways to burn your API credits. In this post, I walk through how I built an enterprise-ready <strong>GenAI Model Router</strong> using Python (FastAPI), Next.js (TypeScript), Google Gemini API, and Hugging Face. We’ll look at static length routing, exponential backoff with jitter, automatic failover, and honest production trade-offs.</p><p>— -</p><p><strong>The $10,000 “Hello World” Problem</strong></p><p>If you’ve built LLM apps in production, you know the drill: your team picks a top-tier frontier model like <strong>Claude Sonnet 5 </strong>or <strong>OpenAI</strong> <strong>GPT-6 Astra </strong>wires up an API key, and calls it a day.</p><p>It works great, until the bill arrives.</p><p>When you analyze user traffic logs, a staggering percentage of real-world queries look like this:</p><p>- <em>*”Hi”*</em></p><p>- <em>*”Thanks!”*</em></p><p>- <em>*”What is 2 + 2?”*</em></p><p>- <em>*”Summarize this sentence.”*</em></p><p>Routing these light 5-word queries to a heavy flagship model costs the exact same base overhead as sending a 5,000-word legal document. Multiply that across thousands of active users, and you’re essentially lighting money on fire.</p><p><strong>The solution? Model Routing.</strong></p><p>By dynamically or statically routing queries to smaller, open-source models (like meta-llama/Llama-3.3-70B-Instruct on Hugging Face) for simple tasks, and reserving premier models (<strong>Claude Sonnet 5</strong>) for complex reasoning, you can reduce LLM infrastructure costs while preserving response quality.</p><p>— -</p><p><strong>The Stack &amp; System Architecture I Built</strong></p><p>Here is the architecture I designed to solve this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/393/1*oWDwGYZVCVWB9U041zybBg.png" /></figure><p><strong>The Tech Stack:</strong></p><ul><li><strong>Backend:</strong> Python 3.11+, FastAPI, Pydantic v2, Tenacity (for retries), Pytest.</li><li><strong>Frontend:</strong> Next.js (TypeScript) styled with Google’s clean four-color light theme (`#4285F4`, `#EA4335`, `#FBBC05`, `#34A853`).</li></ul><p><strong>Models:</strong></p><ul><li><strong>Primary Flagship:</strong> google/gemini-2.5-flash</li><li><strong>Open-source Fast Provider:</strong> meta-llama/Llama-3.3-70B-Instruct (via Hugging Face Serverless Inference API)</li><li><strong>Deployment:</strong> Dockerized and ready for <strong>Google Cloud Run</strong>.</li></ul><p>— -</p><p><strong>Static Routing Rules: Simple, Deterministic &amp; Fast</strong></p><p>While dynamic LLM-as-judge routers are popular, they add latency and extra token costs to decide where to send a prompt. For V1, I implemented a zero-overhead <strong>Static Routing Rule</strong>:</p><pre>def inspect_route(prompt: str) -&gt; RouteInspection:<br>  cleaned = prompt.strip()<br>  word_count = len(cleaned.split())<br>  char_count = len(cleaned)<br>  # Route to Hugging Face if prompt is short<br>  if word_count &lt; 10 or char_count &lt; 10:<br>  return RouteInspection(<br>  primary_route=&quot;huggingface&quot;,<br>  model_name=&quot;meta-llama/Llama-3.3-70B-Instruct&quot;,<br>  reason=f&quot;Short query: {word_count} words &lt; 10&quot;<br>)</pre><p><strong>Route to Google Gemini API if prompt requires heavier reasoning</strong></p><pre>return RouteInspection(<br>primary_route=&quot;google&quot;,<br>model_name=&quot;google/gemini-2.5-flash&quot;,<br>reason=f&quot;Detailed query: {word_count} words &gt;= 10&quot;<br>)</pre><p>If a prompt has <strong>&lt; 10 words or &lt; 10 characters</strong>, it routes to <strong>Hugging Face (</strong>meta-llama/Llama-3.3-70B-Instruct<strong>)</strong>. If it contains <strong>10 words and characters</strong>, it routes to <strong>Google Gemini (</strong>google/gemini-2.5-flash<strong>)</strong>.</p><p>— -</p><p><strong>Resiliency: Why You Need Exponential Backoff with Jitter</strong></p><p>In production, APIs fail. Cloud providers return <em>429 Too Many Requests</em> during traffic spikes, or <em>503 Service Unavailable</em> when models load.</p><p>If your code retries immediately on a fixed 1-second timer, 10,000 concurrent users will hit the provider simultaneously on retry #1, causing a <strong>thundering herd problem</strong> that permanently locks up your backend.</p><p>To prevent this, I wrapped all LLM invocations with <strong>Exponential Backoff and Random Jitter</strong> using Python’s tenacity library:</p><pre>@retry(<br>wait=wait_random_exponential(min=0.5, max=4.0), # ←- Adds randomized jitter<br>stop=stop_after_attempt(3),<br>retry=retry_if_exception_type(GoogleServiceError),<br>reraise=True<br>)<br>def generate_with_retry(prompt: str):<br>return call_google_gemini_api(prompt)</pre><p><strong>Automatic Provider Failover</strong></p><p>If Google API still fails after 3 retry attempts (or if rate limits spike), the router’s <strong>Circuit Breaker </strong>automatically redirects the request to Hugging Face:</p><pre>try:<br>return self.google_service.generate(prompt)<br>except GoogleServiceError as g_err:<br>logger.warning(&quot;Google API failed after retries. Failing over to Hugging Face…&quot;)<br># Dynamic Failover Execution<br>return self.hf_service.generate(prompt)</pre><p>If both providers fail, the router catches the exception and returns a <strong>Graceful Degradation </strong>response to the frontend with detailed diagnostic metadata, ensuring the client UI never crashes hard.</p><p>— -</p><p><strong>Honest Production Caveats: What I Left Out (For Now)</strong></p><p>This project is a functional, tested model router, but here is what is <em>not </em>in V1:</p><p>1. <strong>No Validation Pipeline</strong>: There is no semantic input validation pipeline checking for prompt injections or toxic inputs before routing.</p><p>2. <strong>No Redis Rate-Limiting</strong>: Retry counters and circuit breaker flags are evaluated per-request rather than tracked in a distributed Redis cache.</p><p>3. <strong>Static vs. Dynamic Thresholds</strong>: A 9-word prompt might sometimes require heavy logic, while a 20-word prompt might be boilerplate. Moving to embedding-based routing or classifier-based routing is the natural V2 upgrade.</p><p>— -</p><p><strong>The Developer &amp; User Experience</strong></p><p>I built a custom Next.js UI styled in Google’s signature four-color light theme. It gives real-time visual feedback:</p><ul><li><strong>Live Route Estimator</strong>: As you type, a dynamic pill badge instantly tells you whether your query will hit Hugging Face or Google API.</li><li><strong>Failover Simulation Suite</strong>: Checkboxes allow developers to artificially simulate Google API or HF outages to test failover behavior live.</li><li><strong>Metrics Dashboard</strong>: Displays round-trip latency in milliseconds, provider used, backoff retries performed, and token count.</li></ul><p>— -</p><p><strong>Try It Yourself</strong></p><p>All code for this project, including the FastAPI backend, Pytest suite, Next.js dashboard, Dockerfiles, and Google Cloud Run deployment script,is open source on GitHub:</p><p>🔗 <strong>GitHub Repository:</strong> [https://github.com/ElishebaW/model-router](https://github.com/ElishebaW/model-router)</p><p><strong>Final Thoughts</strong></p><p>Building with AI isn’t just about prompt engineering; it’s about <strong>systems engineering</strong>. Implementing smart routing, retry backoff jitter, and graceful failovers ensures your application stays fast, resilient, and financially sustainable as you scale.</p><p>What routing strategies are you using in your LLM stack? Drop a comment below or connect with me on GitHub!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b454adce54f7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Engineering 100% Accuracy in a Real-Time AI Workout Coach]]></title>
            <link>https://medium.com/@elisheba.t.anderson/engineering-100-accuracy-in-a-real-time-ai-workout-coach-8952b7cc46a6?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/8952b7cc46a6</guid>
            <category><![CDATA[ai-engineering]]></category>
            <category><![CDATA[fitness]]></category>
            <category><![CDATA[coaching]]></category>
            <category><![CDATA[langfuse]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Thu, 28 May 2026 15:01:02 GMT</pubDate>
            <atom:updated>2026-05-28T15:01:02.804Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UYe1FRt9hxbHDPxiAOH-3A.png" /></figure><p>Lately, I’ve been working on Phase 1 of my optimization plan for <strong>Chaos Fit</strong>, a real-time AI workout coach built on top of Gemini Live and bidirectional audio streaming. My immediate goal? <strong>Coaching Accuracy.</strong> When an AI coach is tracking your workout in real time, it cannot guess. If a user does 10 push-ups, the model can’t report 9 or 11. It can’t mistake a minor adjustment for a full rep. It can’t give generic feedback like <em>“Great job, keep going!”</em> when their lower back is sagging during a plank.</p><p>Here is exactly how I’m moving <em>Chaos Fit</em> from brittle prompts to production-grade accuracy, broken down into four engineering phases.</p><h3>Phase 1: Building a Failure Taxonomy from Real Traces</h3><p>You can’t fix what you aren’t measuring. I started by pulling recent session traces from <a href="https://langfuse.com/">Langfuse</a>, focusing specifically on exercise_detection session_summary_generation observations.</p><p>Instead of guessing why the coach missed a rep, I had Claude Code categorize real-world failures into a concrete taxonomy (failure_taxonomy.md):</p><ul><li><strong>F-1 through F-4:</strong> Tracking wrong rep counts, double-counted reps, and false positives.</li><li><strong>F-5:</strong> Tracking rep-count false positives caused by incidental movement.</li></ul><p>With this taxonomy established, Claude Code built a local evaluation dataset (evals/dataset.json) featuring <strong>27 distinct edge cases</strong> spanning rep counts, correction specificity, interruption integrity, and session summaries.</p><h3>Phase 2: Tightening the Prompts and Engineering Evaluators</h3><p>With a solid baseline evaluation dataset, I updated our coach-system-instruction and coach-system-instruction-native-audio prompts. The new guidance explicitly mandates full Range of Motion (ROM) for a rep to count and strictly excludes incidental movement.</p><p>But a prompt change means nothing without automated verification. Claude wrote three custom evaluators:</p><ol><li>eval_rep_count_accuracy: Enforces a strict tolerance, with an absolute zero-tolerance policy when the expected rep count is zero.</li><li>eval_correction_specificity: Eliminates generic fluff.</li><li>eval_interruption_integrity: Ensures the live audio stream handles conversational interruptions flawlessly.</li></ol><p><strong>The result?</strong> 27/27 evaluations are now passing cleanly in local testing. Next up is a fast-follow update to the form feedback engine, mandating a strict [body part] + [corrective action] pattern (e.g., <em>&quot;Drop your hips&quot;</em> instead of <em>&quot;Fix your form&quot;</em>).</p><h3>Phase 3: Advanced Pose Estimation and UI Overlays</h3><p>While Gemini handles the core contextual reasoning, true real-time coaching requires visual validation. The next step of the roadmap introduces deep joint-angle guidance for three primary movements:</p><ul><li><strong>Squats:</strong> Tracking hip and knee depth.</li><li><strong>Push-ups:</strong> Monitoring elbow angles and spinal alignment.</li><li><strong>Planks:</strong> Measuring hip height and shoulder stack.</li></ul><p>I’m currently wiring this structured pose data directly into our frontend canvas layer (app.js). By parsing structured output from the model turns and translating them into pixel coordinates, the app will render dynamic canvas overlays, drawing real-time joint markers and alignment lines directly over the user’s camera feed.</p><h3>Phase 4: Locking it Down with a CI Gate</h3><p>Great engineering systems are self-defending. The final phase of this sprint is integrating these coaching accuracy evaluations directly into our GitHub Actions workflow (.github/workflows/ci.yml).</p><p>By establishing a strict CI gate, we ensure that future prompt updates, infrastructure changes, or model upgrades can never merge unless they pass our 27-case accuracy baseline.</p><h3>The Takeaway</h3><p>Building agentic AI systems that interact with the physical world requires moving past conversational novelty. It requires regression testing, deterministic evaluations, and structured feedback loops.</p><p>If you’re building in the real-time AI or multimodal space, how are you moving past the “vibe check” phase? Let me know in the comments.</p><p><em>Want to catch up on how this project started? Read the original architecture breakdown:</em> <a href="https://medium.com/@elisheba.t.anderson/chaos-fit-building-a-real-time-ai-workout-coach-with-gemini-live-dd595c3f97b6">Chaos Fit: Building a Real-Time AI Workout Coach with Gemini Live</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8952b7cc46a6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Engineers are tracking token costs and acceptance rates.]]></title>
            <link>https://medium.com/@elisheba.t.anderson/engineers-are-tracking-token-costs-and-acceptance-rates-5a7c0dc99215?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/5a7c0dc99215</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[token]]></category>
            <category><![CDATA[metrics]]></category>
            <category><![CDATA[code]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Thu, 14 May 2026 14:11:00 GMT</pubDate>
            <atom:updated>2026-05-14T14:11:00.762Z</atom:updated>
            <content:encoded><![CDATA[<h3>Engineers are tracking token costs and acceptance rates.<br>Here’s what I see still missing, and what Mass Ave Labs built to fix it.</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4i19bhSLTb9unLYSY0SK6A.png" /></figure><p>The industry finally has a measurement conversation. Teams are watching token spend, AI code acceptance rates, and PR throughput. That’s real progress. But from my seat as an engineer building AI systems, I can see the gap those metrics still leave, and it’s where the actual ROI lives.</p><p>Something has shifted in the last year. Engineering managers who couldn’t tell you how many tokens their team consumed in January are now getting monthly invoices that make the number impossible to ignore. <a href="https://code.claude.com/docs/en/costs">Anthropic reports the average Claude Code usage runs around $6 per developer per day, with 90% of users spending under $12 daily </a>, and that’s before multi-agent workflows enter the picture. Token spend is real, it’s growing, and leadership is paying attention.</p><p>So the measurement conversation has started. But I want to make a case that it’s started in the wrong place, and that the metrics most teams are watching right now answer a narrower question than the one that actually matters to the business.</p><p>I’m an engineer, not a CTO. I’ve spent my AI career building AI systems, RAG pipelines, OCR ingestion workflows, and event-driven architectures at federal scale. I got frustrated that I couldn’t answer basic questions about my own AI-augmented workflow, so I built a tool to track them. What I learned in the process is that the missing layer isn’t more spending data, it’s task-level outcome data, and almost no one has it.</p><h3>What engineering teams are already tracking</h3><p>To be fair about what’s happening out there: the measurement conversation in engineering orgs has matured significantly. Based on research from the <a href="https://newsletter.pragmaticengineer.com/p/how-tech-companies-measure-the-impact-of-ai">Pragmatic Engineer’s reporting</a> on how 18 tech companies measure AI impact, the metrics that tend to show up on engineering dashboards look roughly like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/832/1*x8TeEJmezVhxTkQYUkQp2Q.png" /></figure><p>These aren’t bad metrics. PR throughput and code churn are legitimate signals. T-Mobile uses AI code acceptance rate to estimate how much AI-authored code reaches production, which is a genuinely hard problem to solve. Shopify took a different approach entirely, introducing an internal AI leaderboard to celebrate developers who spend the most on token consumption, treating experimentation as something to reward rather than scrutinize.</p><p>But here’s the structural problem with all of these: they measure activity and volume. They don’t measure whether a specific task was completed faster because of AI, how much faster, whether the speed introduced downstream debt, or what the actual cost-per-hour-saved was. Those are the numbers that connect to the P&amp;L, and almost no team has them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/832/1*utS3dca9SECwDsT13VVXCg.png" /></figure><ul><li><a href="https://leaddev.com/the-ai-impact-report-2025">The AI Impact Report 2025</a></li><li><a href="https://blog.exceeds.ai/token-based-ai-coding-budgets/">How Token-Based AI Coding Tools Impact Engineering Budgets</a></li><li><a href="https://jellyfish.co/blog/2025-software-engineering-management-trends/">Engineering in the Age of AI: What the 2025 State of Engineering Management Report Reveals - Jellyfish</a></li><li><a href="https://jellyfish.co/blog/2025-ai-metrics-in-review/">2025 AI Metrics in Review: What 12 Months of Data Tell Us About Adoption and Impact - Jellyfish</a></li></ul><p>That 20% figure is the one that stops me. Sixty-one percent of companies increased their engineering budgets in 2025, yet only 20% of teams say they’re using engineering metrics to measure AI impact. That’s an enormous gap between investment and accountability. The spend is real. The measurement isn’t keeping up.</p><h3>The problem with token cost as a proxy</h3><p>Token spend is increasingly visible, and that’s genuinely good. But on its own, it’s the wrong denominator. It tells you how much you’re spending. It doesn’t tell you what you’re getting for it.</p><p>Consider two tasks. A deep architecture planning session with Claude might consume 200,000 tokens over 90 minutes and redirect a team away from a week of wrong-direction implementation. That’s an extraordinary return on inference spend. A boilerplate CRUD endpoint might consume 60,000 tokens and save 20 minutes. Far lower ROI per token, even if the acceptance rate looks identical in your dashboard.</p><p>The metric that matters is cost-per-hour-saved. Token spend divided by developer time actually recaptured. I haven’t seen this in any standard engineering dashboard. It’s what my tracker computes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9PIn68cxINQan9cPgzwlNw.png" /></figure><h3>What acceptance rate doesn’t tell you</h3><p>AI code acceptance rate has become a go-to metric because it’s easy to pull from vendor APIs. It’s a reasonable proxy for whether developers find the tool’s suggestions useful. But it measures input quality, not output quality.</p><p>A developer can accept an AI suggestion, ship it, and introduce a bug that costs four hours of debugging next sprint. The acceptance rate logs a win. The actual business outcome was negative.</p><p><a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research">GitClear’s analysis of 211 million</a> lines of code found churn rising from a 3.3% pre-AI baseline in 2021 to 5.7% by 2024, and AI-assisted code inherently has higher turnover than human-only code. High acceptance rate plus elevated churn rate is a red flag that most dashboards aren’t wired to surface.</p><p>The question isn’t just “did the developer accept the suggestion?” It’s “did the task ship cleanly, and did it stay shipped?” That requires tracking completion status alongside AI-assist time, which is what the Status field in my tracker does.</p><h3>What I built with Mass Ave Labs and why each field exists</h3><p>I built a tool to track my own AI-augmented work because I couldn’t answer basic questions about it: where am I actually saving time, what’s it costing me per hour saved, which tools are earning their keep on which task types? Every field exists because it closes a loop that standard metrics leave open.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/832/1*kLN3Gc41ZhY1AlvX969aTg.png" /></figure><h3>How the fields answer questions, token dashboards can’t</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/832/1*Pd5X2XxwejpsCAc0PgzWMg.png" /></figure><h3>What this data actually changes</h3><p>Here’s the practical difference task-level tracking makes. Without it, an engineering org making AI investment decisions is working with averages like average acceptance rate, average token spend, and average PR velocity. Averages across task types that behave very differently.</p><p>Bug fixes on medium-complexity tickets tend to generate the highest AI ROI. The work is bounded, the context is clear, and AI can generate a candidate fix that a developer reviews rather than the authors. At $175/hour fully-loaded cost, if AI takes a 2-hour bug fix to 30 minutes, that’s $262 in recaptured capacity per ticket. Across 30 bug tickets a sprint, that’s $7,860 per sprint before you’ve touched any feature work.</p><p>High-complexity architecture decisions are a different story. The gains are real but slower to appear, and the cost of a wrong suggestion is higher. The right response isn’t “use less AI on hard problems”, it’s “use AI differently on hard problems, with more review checkpoints.”</p><p>You can’t build that nuance into your adoption strategy without knowing which tasks are generating which outcomes. The org-level averages will always obscure it.</p><h3>Six things worth tracking that most orgs aren’t</h3><p>If I were advising an engineering org on what to start measuring before buying new tooling, this is the data contract I’d ask for:</p><p><strong>Baseline time, before AI runs</strong>. Estimate how long a task would take without AI assistance, and log it before starting. Engineers who’ve used AI for a year can’t accurately reconstruct this retroactively. It has to be captured in the moment.</p><p><strong>Wall-clock time with AI assistance</strong>. Not perceived effort, not story points. Actual elapsed time. Perception drifts significantly once AI becomes the norm.</p><p><strong>Task type and complexity at the time of work</strong>. Without this segmentation, you’re averaging across categories that behave completely differently. Bug fixes and architecture design are not the same kind of AI task.</p><p><strong>Which tools ran on which task</strong>. Seat utilization tells you a license is being used. Task-level tool logging tells you whether it’s being used where it generates the most value. These are very different signals.</p><p><strong>Completion status and rework.</strong> Did the AI-assisted task ship cleanly, or did speed come with downstream cost? This is the quality question acceptance rate can’t answer.</p><p><strong>Token consumption per task. </strong>Not for surveillance — for optimization. When you can pair token spend with time saved per task type, you can answer the question the CFO is eventually going to ask: what is our productive work per dollar of inference?</p><p>These are the fields my tool captures. I built it for my own workflow. But the gap it fills exists at every team that’s watching their token bill go up and wondering whether the investment is compounding or just accumulating.</p><p>Tool: <a href="https://massavelabs.com/impact-tracker">https://massavelabs.com/impact-tracker</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5a7c0dc99215" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Build: My Q2–2026 AI Engineering Stack]]></title>
            <link>https://medium.com/@elisheba.t.anderson/how-i-build-my-q2-2026-ai-engineering-stack-085b941ab511?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/085b941ab511</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[party-rock]]></category>
            <category><![CDATA[gemini-cli]]></category>
            <category><![CDATA[workflow]]></category>
            <category><![CDATA[openai-codex]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 15:26:01 GMT</pubDate>
            <atom:updated>2026-04-29T15:26:01.213Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UAgxZixatS82H-Q5zblxVw.png" /></figure><p>For me, the shift from using AI to <em>engineering</em> with AI happened the moment I stopped reaching for tools and started building a system. Each piece of my stack now has a specific, non-negotiable job. Whether it’s using Claude for architectural reasoning, Party Rock for rapid utility apps, or a local Llama instance to audit my own productivity, this is the infrastructure that powers my work at Mass Ave Labs.The Stack at a Glance</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ni5fNRfuKl2W8VYE87-LOQ.png" /></figure><h3><strong>The Core Infrastructure</strong></h3><ul><li><strong>Languages &amp; Frameworks:</strong> Python, LangChain, MCP (Model Context Protocol).</li><li><strong>Local Inference &amp; Models:</strong> Ollama, Hugging Face.</li><li><strong>Data &amp; Memory:</strong> ChromaDB and SQLite</li></ul><h3>The Reasoning &amp; Execution Engines</h3><h4>Claude Code — Sonnet 4.6— The Architectural Brain</h4><p>Claude Code is where I do my most demanding thinking. It reasons at a level that’s genuinely useful for senior engineering decisions, how to structure a pipeline, which patterns to reach for, and what the tradeoffs are between approaches. It runs in the terminal as an agent; it can work through architectural problems across the entire codebase rather than just suggesting isolated snippets. The MCT here is the AskUserQuestion tool, which asks me questions to generate the best architecture.</p><h4>Codex &amp; Google CLI — GPT 5.4 and Gemini 3 Pro/Flash— The Agentic Backbone</h4><p>I rely on a mixture of <strong>Codex</strong> and <strong>Google Gemini</strong> for the coding itself. These models handle the heavy lifting of generating the logic and implementation details. This allows my agents to interact directly with cloud infrastructure and local environments, turning the AI from a “chatting partner” into a system operator that can execute commands and manage resources in real-time.</p><h4><strong>Windsurf — SWE 1.6 — Agentic IDE</strong></h4><p>The agent for small issues like GitHub resolutions, and the agentic IDE for all my coding.</p><h3>Governance &amp; Rapid Utility</h3><h3>Prompt Repo &amp; Promptfoo — Managing the “Logic”</h3><p>If prompts are the new source code, they belong in a repository, not just a scratchpad. I maintain a central GitHub repo for my prompts to ensure version control across all my Mass Ave Labs projects. Right now, I’m using <strong>Promptfoo</strong> as the framework for this library. Even while I’m in the phase of simply saving and organizing my best-performing prompts, the infrastructure for systematic evaluation is already there. When I’m ready to benchmark a new RAG pipeline, I don’t have to start from scratch.</p><h3>Party Rock and Replit— Making One-Off Apps</h3><p><strong>Party Rock</strong> is my go-to for making one-off apps. I don’t need a full deployment cycle for a tool I’ll use ten times. I’ve used it to build a <a href="https://partyrock.aws/u/eanders/_IsvXsGlc/LinkedIn-Connection-Message-Composer">LinkedIn connection composer</a> and an A<a href="https://partyrock.aws/u/eanders/ALe73CQl8/AI-Career-Navigator">I Career Navigator</a>. I’ve built one app on Replit. The only drawback is on the free plan the app expires in 30 days.</p><h3>The Feedback Loop</h3><h3>Wispr Flow — Speaking to AI</h3><p>This allows me to speak my prompts. When you speak, you’re more conversational and exploratory. The outputs are better because the inputs are richer. Most of my technical writing and complex architectural prompts start as spoken thoughts here.</p><h3>The Stack Watching Itself: MCP + Gemini</h3><p>This is the piece I built myself. My <strong>MCP server</strong> logs every task — estimated time without AI vs. actual time with AI. <strong>Gemini 2.5 Flash </strong>analyzes this data and creates STAR stories. This closes the loop. Most tools make you faster in the moment; this one tells me if I’m actually getting better over time.</p><p>The shift from adoptive to transformative isn’t about using more tools. It’s about building a system that gets smarter as you use it.</p><p><strong>What does your stack look like for Q2? Let’s talk about it in the comments.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=085b941ab511" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My exact AI-assisted architecture review workflow]]></title>
            <link>https://medium.com/@elisheba.t.anderson/my-exact-ai-assisted-architecture-review-workflow-1cbdd576b6da?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/1cbdd576b6da</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <category><![CDATA[workflow]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Wed, 15 Apr 2026 15:26:01 GMT</pubDate>
            <atom:updated>2026-04-15T15:26:01.610Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9QBYRXUFXCcSxPnkWpJoYg.png" /></figure><p>I’m going to show you my workflow for exactly what I did on a real project, Global Impact Tracker, a tool I’m building with a Pro tier gated behind Stripe payments and license key validation. I’ll show you the prompts I actually used, spoken through Wispr Flow into Claude. The flaw that surfaced. The three options we worked through. Why I picked the one I did. And how I got from architectural decision to implemented code.</p><p>This is the workflow.</p><h3>The context: what I was building</h3><p>Global Impact Tracker needed a Pro tier. The plan was straightforward: Stripe handles payment, a license key gets generated on successful purchase, and a is_pro() function gates Gemini features behind that key. I planned it with Claude before writing a line of code. The direction felt solid. I built an MVP, a valid_keys.txt file on my machine that is_pro() reads to validate keys.</p><p>It worked. I tested it. It looked complete.</p><p>That’s the dangerous moment in any system, when something works for you, and you mistake that for it working.</p><h3>Phase 1: before — planning with AI</h3><p>Before writing code, I use Claude to pressure-test the plan. Not to generate the plan, but to poke at it. I route this to Claude specifically because I need something that can reason about architecture tradeoffs, not just autocomplete my thinking.</p><p><strong>Model:</strong> Claude Code via Wispr Flow.</p><p>A typical before-phase prompt sounds like this, spoken, not typed:</p><blockquote><strong><em>Prompt — spoken via Wispr Flow</em></strong></blockquote><blockquote>I’m building a Pro tier for Global Impact Tracker. Stripe handles payment, generates a license key, and I have an is_pro() function that validates the key and gates Gemini features. The plan is to store valid keys in a file and check against that. What should I be thinking about before I build this?</blockquote><p>What I expect back at this stage: a short list of considerations I might have missed, framed around my specific situation. Not a generic architecture lecture. Claude knows the context, the indie tool, private repo, no external users yet, and the output should reflect that. If it’s giving me enterprise-scale concerns for a one-person project, I push back.</p><p>The before-phase caught nothing critical this time. The plan looked sound. I built the MVP.</p><h3>Phase 2: during — building with a placeholder</h3><p>The valid_keys.txt approach was always meant to be a placeholder, an MVP that let me build the rest of the system while deferring the harder question of how validation actually works at scale. That&#39;s a legitimate pattern. Build the interface, defer the implementation.</p><p>What I didn’t fully reckon with was the deployment model. And that’s what the after-phase surfaced.</p><h3>Phase 3: after — stress-testing the mental model out loud</h3><p>I explain the system out loud, end-to-end, narrating the user journey as if I’m describing it to someone who doesn’t know the codebase. I do this through Wispr Flow into Claude. The act of narrating forces precision. Vague mental models don’t survive being spoken aloud.</p><p>This is what I said:</p><blockquote><strong><em>Prompt — spoken via Wispr Flow</em></strong></blockquote><blockquote>How exactly does my server on my machine validate their key if they’re using it on another machine? If they’re calling the server through the config on another machine, how does my machine know to check their Stripe license key? That’s what I am getting?</blockquote><blockquote>Right now, Global Impact Tracker is private. The free part needs to be public so I can share it, but the MCP part needs to be private so people can’t see it. What does the Stripe license key actually do? Right now it’s saved into a valid_keys.txt file on my computer. How does that make sure someone’s pro?</blockquote><h3>What the prompt surfaced</h3><p>The answer was that it doesn’t.</p><p>The valid_keys.txt file lives on my machine. is_pro() reads it on my machine. For any real customer running the tool on their own machine, is_pro() would always return Falsebecause their machine has no valid_keys.txt.</p><p>The system worked perfectly because I am both the developer and the only user. That’s not validation. That’s a local file check dressed up as access control.</p><p>This is the thing I want to be clear about: Claude didn’t catch this by reading the code in isolation. I did, by being forced to narrate the user journey end-to-end. The narration exposed a gap in the mental model that the code itself didn’t reveal. The flaw was in the deployment assumption, not in the logic. And deployment assumptions don’t live in code. They live in how you think about the system.</p><p>Wispr Flow mattered here. Speaking the prompt rather than typing it meant I was more conversational, more exploratory.</p><h3>Working through the options</h3><p>Once the flaw was on the table, Claude Code laid out three approaches. My job was to pick the right one for my specific situation, not the theoretically best one in the abstract.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/1*McLV1eklisSFwUI0ogHY0w.png" /></figure><h3>Why signed keys won for this situation</h3><p>Global Impact Tracker has no paid users yet. Serverless validation is the right answer at scale, but standing up cloud infrastructure before a single customer exists felt premature. Signed keys solve the immediate problem: any machine can validate a key cryptographically without phoning home, without a server, and without exposing anything in the repo. It’s private by design and scales iteratively. Most systems don’t need to be fully scaled up front. They need to be built so scaling is possible later. Signed keys buy that option without paying for infrastructure you don’t need yet.</p><h3>How the decision became code</h3><p>Once I had the decision, I didn’t go straight to implementation. I routed back to Claude with a specific instruction:</p><blockquote><strong><em>Implementation prompt</em></strong></blockquote><blockquote>Make a plan for changing the is_pro() validation from a local valid_keys.txt file to signed key validation. Show me the plan first. I’ll review it before you implement anything.</blockquote><p>This is a pattern I use consistently, and I think it’s one of the highest-leverage habits in AI-assisted development: <strong>always ask for the plan before the code.</strong> It costs almost nothing, and it gives you a moment to catch misalignment before it’s baked into an implementation.</p><p>I reviewed the plan. It was right. Then I said implement. Claude Code reasons across the whole codebase, writes across files.</p><h3>The workflow, step by step</h3><p><strong>1. Plan with Claude Code / Wispr Flow</strong> — Architectural direction, tradeoff analysis, option selection. Spoken prompts. Conversational register. No code yet.</p><p><strong>2. Request the plan before the code —</strong> “Show me the plan first.” Review it. Catch misalignment before it’s implemented. This is the step most people skip.</p><p><strong>3. Provide context</strong> — Before implementation starts, feed Claude Code the relevant files and documentation it needs. Point it at the parts of the codebase it should pay attention to. Don’t make the model guess at your architecture. The quality of what it produces is directly proportional to the context it has.</p><p><strong>4. Implement in phases, test after each one</strong> — Claude Code implements step by step. After each phase, tests run and I review the output before proceeding to the next step. This is not optional. Phased testing is what keeps a multi-step implementation from compounding errors across stages.</p><p><strong>5. Review the code yourself</strong> — Read what the model produced. Don’t ship what you haven’t read.</p><blockquote><strong><em>Step 5 is not ceremonial.</em></strong><em> On a previous Claude Code implementation in this same project, my code review caught a missing OWASP control around injection and a line that needed simplification. The model got the structure right and left a security gap. The review caught it before it shipped.</em></blockquote><p><strong>6. Stress-test by narrating out loud —</strong> Explain the system end-to-end as if describing it to someone who doesn’t know the code. Speak it through Wispr Flow. Vague mental models don’t survive being spoken aloud.</p><h3>What this workflow actually is</h3><p>I want to name what’s happening here, because I think it gets mischaracterized. This isn’t “using AI to write my architecture.” The architectural judgment is mine: what to build, what constraints matter, what tradeoffs are acceptable for this specific stage of this specific project. What AI provides is a thinking partner that doesn’t get tired, doesn’t have a stake in the decision, and will tell me honestly when my mental model has a hole in it.</p><p>The before phase catches things I haven’t thought of. The during phase builds the system. The after phase is where I narrate my way into the truth. And the narration, spoken, conversational, sometimes uncertain, is the most important part. “That’s what I am getting?” isn’t a polished prompt. It’s a real question. And real questions get real answers.</p><p>That’s the workflow.</p><h3>What I’m adding next</h3><p>This workflow caught a real architectural flaw and a security gap in AI-generated code. But writing it up forced me to see where it can get sharper.</p><p>The first thing I’m adding is a formal spec.md before I touch code. A structured document that captures requirements, architecture decisions, edge cases, and testing strategy before the first prompt. Right now, I plan conversationally. A spec.md makes that planning an artifact I can reference, share, and build on.</p><p>The second thing is a prompt plan, a written, sequenced list of prompts for the implementation phase, saved as a file rather than improvised turn by turn.</p><p>And the third is automated quality gates around AI output, linting, and static analysis running on every phase, not just my manual review. My code review caught the OWASP injection issue. A tool should be catching that class of problem automatically, before it even gets to me. The pattern holds at every level. AI caught what I missed in the mental model. I caught what AI missed in the implementation.</p><p>One more experiment I’m running: using Windsurf’s SWE-1.5 model for the planning and architecture phase, then handing off to Claude Code for implementation. The reasoning is straightforward: if a strong planning model can get the spec and structure right up front, then Claude Code’s Pro membership usage goes toward the work it does best: the actual build. I want to see whether splitting the workflow across models by phase yields better results than running everything through a single model. That’s the next thing I’m testing.</p><p>The goal isn’t a perfect workflow. It’s a workflow where fewer things slip through on both sides.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1cbdd576b6da" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reason #1,900 Why AI Won’t Replace Us: The Model Wasn’t Broken.]]></title>
            <link>https://medium.com/@elisheba.t.anderson/reason-1-900-why-ai-wont-replace-us-the-model-wasn-t-broken-d27bb7489791?source=rss-7f60e4e112c5------2</link>
            <guid isPermaLink="false">https://medium.com/p/d27bb7489791</guid>
            <category><![CDATA[model]]></category>
            <category><![CDATA[mlops]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[vertex-ai]]></category>
            <category><![CDATA[mls]]></category>
            <dc:creator><![CDATA[Elisheba Builds]]></dc:creator>
            <pubDate>Wed, 25 Mar 2026 14:26:00 GMT</pubDate>
            <atom:updated>2026-03-25T14:26:00.555Z</atom:updated>
            <content:encoded><![CDATA[<h3>Reason #1,900 Why AI Won’t Replace Us: The Model Wasn’t Broken. My Deployment Pipeline Was, and I couldn’t get AI to tell me initially</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eyLJ0ETXNXmSICs2Y8ZzWw.png" /></figure><p>I thought I broke my model.</p><p>After retraining, I deployed to Vertex AI, and the endpoint sat in “Deploying/Creating” forever. At first, I did what most of us do under pressure: I assumed the last thing I touched was wrong. In my case, that meant retraining. I kept redeploying and second-guessing feature engineering, artifact generation, and schema output.</p><p>The training logs were clean. Artifacts were generated. But deployment still looked stuck.</p><p>This is where I had to slow down and stop guessing.</p><h3>What I assumed (and ruled out)</h3><p>My first failed assumption was: “I must have changed training in a way that broke serving.”</p><p>I retrained again, same problem.</p><p>Second failed assumption: “The new model itself is bad.”</p><p>Once I understood the model registry flow better in GCP, I tried deploying an older model. That worked. That was the key directional signal: the model logic wasn’t the root issue. The infrastructure/package path was.</p><p>Third failed assumption: “There are no logs, so there’s nothing to debug.”</p><p>There were logs, but my filter was too narrow in Logs Explorer. Once I broadened the query, I finally saw the real error:</p><p>exec /usr/bin/sh: exec format error</p><p>That line changed everything.</p><h3>Why exec format error pointed to architecture mismatch</h3><p>exec format error usually means the runtime is trying to execute a binary built for a different CPU architecture than the host environment.</p><p>In practical terms: if I build a container image locally on Apple Silicon, it can produce linux/arm64 image layers. Vertex serving environments for this setup expect linux/amd64 (x86_64). So the container starts to initialize, tries to run shell/entrypoint commands, and fails immediately because the binary format is incompatible.</p><p>That explains the “stuck deploy” feeling:</p><ul><li>endpoint shows deploying,</li><li>app-level logs are sparse or misleading,</li><li>retries happen in the backend,</li><li>and it looks like model behavior, when it’s actually image runtime compatibility.</li></ul><h3>What fixed it</h3><p>I rebuilt the serving image in a way that guaranteed compatible architecture (Cloud Build / amd64 path), uploaded model artifacts again, registered a fresh model version, and deployed to a clean endpoint.</p><p>Deployment worked.</p><p>Not a training bug.</p><h3>What I changed in my process</h3><ol><li>I now treat deployment as a separate system from training quality.</li><li>I validate runtime image architecture before spending cycles on retraining.</li><li>I use explicit model and endpoint IDs when deploying (no implicit “latest”).</li><li>I’m building reliable deploy/undeploy scripts, which was my original task anyway, so I can iterate safely and control costs.</li></ol><h3>Two real takeaways</h3><p>AI won’t replace us. Context still matters. I couldn’t prompt my way out until I understood enough GCP context to ask the right questions.</p><p>And you have to know your tools well enough to give explicit instructions to AI.</p><p>Once I provided specific signals (registry behavior, endpoint state, log error, CLI constraints), the debugging got sharp and fast.</p><p>This wasn’t a model failure story.</p><p>It was a systems debugging story.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d27bb7489791" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>