← Back to Blog
For: AI Engineers, ML Engineers, Platform Engineers, AI Systems Architects

Human-in-the-Loop at Production Scale: The Checkpoint Membrane

A blocking approval gate is not safety - at scale it buys you throughput collapse and rubber-stamping. Build a selectively permeable checkpoint with a defined default for when no human answers.

#human-in-the-loop#agent-approval#langgraph#multi-agent-systems#production-ai#approval-fatigue

The agent fleet went live behind a Slack approval gate. Every action a tool wanted to take - send an email, file a refund, push a config change - paused and waited for a human to click Approve. For the first week it felt responsible. By the third week the reviewer was approving roughly nine of every ten requests without reading past the first line, because reading every one was not a job a person could actually do at the volume the fleet produced.

Then a Monday spike doubled the request rate. The approval queue, which a human had been draining at maybe forty decisions an hour, started arriving at ninety an hour. The queue did not slow down gracefully. It grew without bound, the way any queue does when arrivals outrun service, and within ninety minutes every agent in the fleet was blocked on a pending approval. The pipeline was not down. It was waiting - which, to everyone downstream, looked exactly like down.

The post-incident review reached for the obvious fix: add more reviewers. That treats the symptom. The gate itself was the defect. A synchronous human approval on every action does not make an agent system safe. It makes it slow and, worse, it manufactures the precise conditions under which humans stop providing oversight at all.

The thesis: a blocking approval gate is not safety, it is a bottleneck that fakes safety

Here is the claim this article defends: a synchronous, human-blocks-every-action approval gate is not a safety mechanism at production scale - it is a liability that trades one failure (unsafe autonomy) for two worse ones (throughput collapse and rubber-stamping under approval fatigue). The fix is not more reviewers or a faster queue. It is to stop treating human oversight as a wall every action must climb, and start treating it as a selectively permeable membrane keyed on how risky and how reversible each action is.

The consensus is comfortable: agents are dangerous, so put a human in the loop. Make them approve things. The more sensitive the system, the more you make them approve. This feels like diligence. The evidence says it is the opposite. Anthropic measured its own users approving 93% of Claude Code permission prompts, and named the mechanism directly: "approval fatigue, where people stop paying close attention to what they're approving." They did not respond by adding more prompts. They built an automated approval layer to remove them, because a prompt approved 93% of the time is not oversight - it is a click tax that produces the appearance of a human decision while delivering none of its judgment.

So the consensus belief - "more human approval steps make the system safer" - is wrong past a certain volume. Past the point where a human can actually read and reason about each request, every additional mandatory approval lowers real safety while raising latency. The question is not whether to keep a human in the loop. It is which actions deserve one, and what happens at the actions that do not.

Why this matters: oversight has a throughput ceiling and a fatigue floor

Two independent forces break the naive gate, and they come from different fields.

The first is queueing. A human reviewer is a server with a finite service rate. If checkpoints arrive faster than the human clears them, the queue length grows without bound - the instability condition every single-server queue has when arrivals outpace service. (Little's Law gives the steady-state relationship between queue length, arrival rate, and wait time; the point here is that above saturation there is no steady state to settle into.) There is no backoff, no graceful degradation; the wait time climbs until every upstream agent is parked. A gate that works fine in a demo at three requests an hour becomes a fleet-wide stall at ninety, and nothing about the code changed. The capacity ceiling was always there; load found it.

The second is human factors, and it is the one engineers underestimate. Automation complacency is not a discipline problem you can train away. Parasuraman and Manzey, integrating two decades of studies, found that complacency and automation bias appear under multitasking load, afflict experts as readily as novices, and are not eliminated by practice or instructions to be careful. When a person is asked to vet a high volume of mostly-fine requests, they drift toward accepting by default. Security has watched this play out at its sharpest: in MFA-fatigue attacks, an attacker spams push prompts until a worn-down user taps Approve once - which is how the 2022 Uber breach started. The synchronous approval gate, under volume, becomes the attack surface, because the human will eventually approve to make the prompts stop.

Put the two together and the naive gate is trapped between a throughput ceiling it cannot raise and a fatigue floor it cannot train below. You can have a gate that blocks everything and stalls, or a gate that blocks everything and gets rubber-stamped. Both feel like safety. Neither is.

There is a name worth giving the point where this turns: the rubber-stamp threshold - the approval rate past which human review provably decays into auto-approval. Anthropic's 93% is one measurement of a system sitting well above it. Microsoft's finding that roughly 1% of users approve the very first unexpected MFA push they receive is another, from the opposite end - even a single out-of-context prompt is blind-accepted often enough to breach an enterprise. The threshold is lower than intuition suggests, and the design consequence is blunt: once a checkpoint's volume sits above it, every additional checkpoint moves the system further from safety, not closer. You cannot out-staff this and you cannot train past it. You can only stop asking humans about things that do not need them.

The wrong way: one synchronous gate wrapped around every action

This is the pattern that ships in most human-in-the-loop (HITL) agent systems. In LangGraph it usually looks like a single gate node that interrupts on every tool call and waits for a human verdict.

code
from typing import TypedDict, Optional, Literalfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.types import interrupt, Commandclass AgentState(TypedDict):    pending_action: dict    result: Optional[str]# WRONG: every action blocks on a human, regardless of what it is.def gate(state: AgentState) -> Command[Literal["execute", "abort"]]:    action = state["pending_action"]    decision = interrupt({"action": action["describe"]})   # halts the run, every time    if decision == "approve":        return Command(goto="execute")    return Command(goto="abort")

Three things are wrong here, and they are not style nits.

It has no notion of risk. A read-only database query and an irreversible production deploy take the same path and impose the same human cost. The cheap, reversible actions - which are the overwhelming majority - generate the volume that drives the queue past the human's service rate. You pay the full oversight tax on exactly the actions that least need it.

It has no defined behavior when no human answers. interrupt() does not time out. The LangGraph documentation is explicit: it "saves the graph state... and waits indefinitely until you resume." There is no built-in expiry. So this gate has no answer to the most common production condition - the approver is asleep, on PTO, or buried - other than to wait forever. The run is not failed and not done; it is a zombie holding state.

It conflates "a human looked" with "a human judged." Because it asks for everything, it trains the human to approve everything. By the time it asks about the one action that actually matters, the reviewer is three weeks into a 93%-approve reflex and clicks through it too.

A faster queue does not fix any of these. They are properties of the shape of the gate, not its speed.

The right way: a checkpoint that is permeable by risk, with a default for the absent human

Replace the wall with a membrane. A membrane is selectively permeable: some things cross freely, some are slowed, some are stopped at the boundary. The thing that decides is the action's risk and reversibility - what I call the permeability key: a pure function from an action's risk profile to one of four crossing rules.

AGENTIC AI · CHECKPOINT MEMBRANEPermeability Tiers and the Absent-Human DefaultRisk decides how an action crosses the boundary - and what happens when no human answers.Tier 0 · TransparentReversible, low blastAuto-pass, loggedTier 1 · Async ReviewCostly but reversibleProceed, flag for reviewTier 2 · Async ApprovalSlow / hard to reverseWait; halt or escalateTier 3 · Sync ApprovalIrreversible / high blastBlock; deny on timeoutSelectively PermeableEach tier, its owncrossing ruleState-PreservingDurable across the wait;no re-approval on crashLoad-DecoupledHuman latency nevergates throughputDefined Absent DefaultTimeout is a policy,not an exceptionTHE GATE TRAPWhy blocking gates fail at scaleA synchronous gate trades unsafe autonomyfor two worse failures: throughput collapseand rubber-stamping under approval fatigue.The second one still feels like safety.Safety is a membrane, not a wall.DESIGN RULEMake the absent human first-classEvery checkpoint declares three things:who approves, the expiry window, and theaction when no one answers in time.Reversible on timeout -> proceed and flag.Irreversible on timeout -> fail closed.

The four tiers come straight from two questions every action can be scored on: how reversible is it, and how big is the blast radius if it is wrong.

  • Tier 0 - Transparent. Reversible, low blast radius. A read, a draft, a dry-run. It crosses freely; it is logged but never waits for a human.
  • Tier 1 - Async Review. Reversible but costly or noticeable - spends money, sends a message, mutates non-critical state. It crosses now and is flagged for a human to review after. The human is on the loop, not in it.
  • Tier 2 - Async Approval. Slow or hard to reverse. It must wait for a human, but asynchronously - the run parks, durable, and a human approves whenever they get to it. If no one does within the window, it does not proceed; it safe-halts and escalates.
  • Tier 3 - Sync Approval. Irreversible or high blast radius - a production deploy, a large transfer, a destructive operation. It blocks at the boundary and stays blocked until an authorized human approves. If no one answers in time, it fails closed.

Here is the permeability key as code - a pure function, deliberately, because everything before a LangGraph interrupt() re-runs on resume and must be safe to run twice.

code
from dataclasses import dataclassfrom enum import IntEnumfrom typing import TypedDict, Optionalclass Tier(IntEnum):    TRANSPARENT = 0     # reversible, low blast   -> auto-pass, logged    ASYNC_REVIEW = 1    # costly but reversible    -> proceed, flag for review    ASYNC_APPROVAL = 2  # slow / hard to reverse   -> wait; halt or escalate    SYNC_APPROVAL = 3   # irreversible / high blast -> block; deny on timeout@dataclass(frozen=True)class ActionRisk:    reversible: bool    blast_radius: str   # "low" | "medium" | "high"    cost_usd: floatclass PendingAction(TypedDict):    risk: ActionRisk          # the safety-critical field, typed - not a bare dict value    describe: str# Tighten the naive `pending_action: dict` from the wrong-way version into a# typed checkpoint payload, so the classifier's input is type-checked.class AgentState(TypedDict):    pending_action: PendingAction    result: Optional[str]def permeability_key(risk: ActionRisk) -> Tier:    if not risk.reversible or risk.blast_radius == "high":        return Tier.SYNC_APPROVAL    if risk.blast_radius == "medium":        return Tier.ASYNC_APPROVAL    if risk.cost_usd > 1.0:        return Tier.ASYNC_REVIEW    return Tier.TRANSPARENT

The absent-human default - what happens when the window expires - is not a free-form choice per call. It is derived from the tier, so it cannot be forgotten:

code
# Expiry window and the absent-human default are properties of the tier,# not decisions a caller makes (or forgets) at each checkpoint.EXPIRY_SECONDS = {Tier.ASYNC_APPROVAL: 4 * 3600, Tier.SYNC_APPROVAL: 30 * 60}def absent_human_default(tier: Tier) -> str:    # Reversible-but-gated work preserves state and escalates.    # Irreversible work fails closed - the Saltzer-Schroeder fail-safe default.    return "safe_halt" if tier == Tier.ASYNC_APPROVAL else "abort"

Now the gate node. It routes Tier 0 and Tier 1 through without ever calling interrupt(), so the cheap, high-volume actions never touch the human queue. Only Tier 2 and Tier 3 interrupt - and when they do, the resume signal can be one of three things, not two.

code
def gate(state: AgentState) -> Command[Literal["execute", "abort", "safe_halt"]]:    action = state["pending_action"]    tier = permeability_key(action["risk"])   # pure: safe to re-run on resume    # Tier 0/1 return BEFORE interrupt() - they never block the run, so the    # side effects below fire exactly once (these paths never reach the re-run).    if tier == Tier.TRANSPARENT:        log_action(action, decision="auto-pass")        return Command(goto="execute")    if tier == Tier.ASYNC_REVIEW:        enqueue_for_review(action)            # out-of-band; human reviews after        return Command(goto="execute")    # Tier 2/3: park the run durably and wait for a resume signal.    # interrupt() pauses here; on resume it returns the payload from    # Command(resume=...). On resume the node re-runs from the top, which is    # why all code above this line is pure.    decision = interrupt({        "action": action["describe"],        "tier": int(tier),        "approver": route_to_approver(action),  # pure lookup; if it pages, key it - re-runs on resume        "expires_in_s": EXPIRY_SECONDS[tier],    })    if decision["status"] == "approved":        return Command(goto="execute")    if decision["status"] == "rejected":        return Command(goto="abort")    if decision["status"] == "expired":        return Command(goto=absent_human_default(tier))   # the expiry branch    raise ValueError(f"unknown resume status: {decision['status']!r}")

Two resume paths feed that node, and they are symmetric - both are ordinary LangGraph resumes, which is what makes the absent human a first-class control-flow outcome rather than an error to catch. A human approving:

code
def submit_human_decision(graph, thread_id: str, status: Literal["approved", "rejected"]):    graph.invoke(        Command(resume={"status": status}),        config={"configurable": {"thread_id": thread_id}},    )

And the expiry sweeper - an external worker, because LangGraph has no built-in timeout for a waiting interrupt. This is the load-bearing detail: nothing in the framework will ever wake a parked interrupt on its own. If you do not build this, "wait for approval" silently means "wait forever."

code
import time# Runs as a cron/worker OUTSIDE the graph. LangGraph waits indefinitely on an# interrupt; the absent-human default only fires because this resumes it.def sweep_expired_checkpoints(graph, pending_store):    now = time.time()    for item in pending_store.list_pending():           # thread_id, created_at, expires_in_s        if now - item.created_at < item.expires_in_s:            continue        graph.invoke(            Command(resume={"status": "expired"}),            config={"configurable": {"thread_id": item.thread_id}},        )

And the terminal nodes, wired into a graph compiled with a durable checkpointer so a parked Tier 2 approval survives a process restart - the reviewer can approve hours later, after a deploy, after a crash, and the run resumes from the exact checkpoint.

code
from langgraph.checkpoint.postgres import PostgresSaverdef execute(state: AgentState) -> AgentState:    return {"result": run_action(state["pending_action"])}def safe_halt(state: AgentState) -> AgentState:    # Tier 2 expiry: state is already durable in the checkpointer.    # Escalate out-of-band; the run stays resumable, nothing is lost.    escalate(state["pending_action"])    return {"result": "halted: awaiting escalation"}def abort(state: AgentState) -> AgentState:    return {"result": "denied"}builder = StateGraph(AgentState)builder.add_node("gate", gate)builder.add_node("execute", execute)builder.add_node("safe_halt", safe_halt)builder.add_node("abort", abort)builder.add_edge(START, "gate")          # gate routes onward via Command(goto=...)builder.add_edge("execute", END)builder.add_edge("safe_halt", END)builder.add_edge("abort", END)# PostgresSaver.from_conn_string() is a context manager, not a saver. Enter it# once at startup and call setup() on first run to create the checkpoint tables;# serve the compiled graph for the process lifetime from inside the context.with PostgresSaver.from_conn_string(DB_URL) as checkpointer:    checkpointer.setup()                       # one-time: create checkpoint tables    graph = builder.compile(checkpointer=checkpointer)    serve(graph)                               # run the app within the live context

The difference from the wrong way is not a faster queue. It is that the queue only ever sees Tier 2 and Tier 3 - the small minority of actions that genuinely need a human - so the human's service rate now comfortably exceeds the arrival rate of things that actually require judgment. The 93%-rubber-stamp dynamic does not get a chance to form, because the human is no longer asked about the reversible 90%.

The named concept: the Checkpoint Membrane

I call this pattern the Checkpoint Membrane: human oversight modeled as a selectively permeable boundary rather than a wall, with four properties that the wrong-way gate lacks.

  1. Selectively permeable. Each tier has its own crossing rule, set by the permeability key. Reversible, low-stakes actions pass freely; irreversible ones are stopped at the boundary. The membrane does not treat a database read like a wire transfer.
  2. State-preserving. A parked checkpoint is durable. It survives crashes and deploys, and resumes from the exact point of interruption - no work is redone, no approval is re-requested because a pod restarted.
  3. Load-decoupled. Human latency never gates throughput for the actions that do not need a human. Tier 0 and Tier 1 traffic flows at machine speed; only Tier 2/3 enters a human queue, and that queue is sized to a rate a human can actually serve.
  4. Defined absent default. Every checkpoint declares, up front, what happens when no human answers in time. The timeout is a policy, not an exception that surfaces as a hung run.

That fourth property earns its own name, because it is the one teams forget. The absent-human default is the rule that every gated checkpoint must declare three things before it ships: who can approve it, the expiry window, and the action taken when no one answers within that window. The choice of default follows reversibility directly: reversible-but-gated work (Tier 2) preserves state and escalates; irreversible work (Tier 3) fails closed - the fail-safe default Saltzer and Schroeder argued for in 1975, that access decisions should be based on explicit permission, so the absence of a decision denies rather than permits.

The novelty here is not the idea that risky actions need more oversight, or that timeouts need fallbacks - both are old. It is the formal claim that every gated checkpoint has three outcomes, not two: approve, reject, and expire. Most HITL code models two and treats the third as an error - a hung run, a caught timeout, a dropped task. The membrane makes expiry a designed control-flow path with the same standing as approval, chosen at the same time as the approval path rather than discovered in production when a run hangs because the approver went on vacation. Pair that with the rubber-stamp threshold - the volume above which an approval path stops being oversight at all - and you have the two facts that decide whether a checkpoint is real: it must sit below the threshold where humans still read, and it must define what happens when they do not answer.

Human-in-the-loop edge cases that break in production

The expiry branch is not an error handler. The single most common HITL bug is treating "no response" as an exception - a timeout that throws, gets caught somewhere, and either crashes the run or silently drops it. In the membrane, expiry is a normal resume that carries status: "expired" and routes through the same Command(goto=...) machinery as approval. There are three outcomes to every gated checkpoint, not two. If your code has an approve path and a reject path and nothing else, you have not built a HITL system - you have built a system that works only while humans are awake.

Everything before interrupt() runs twice. LangGraph re-executes the interrupted node from its top on resume. Any side effect placed before the interrupt() call - sending a notification, charging a card, writing a row - happens once when the run parks and again when it resumes. This is why the gate node does pure classification before the interrupt and pushes all side effects either to the early-return Tier 0/1 paths (which never interrupt) or to the terminal nodes (execute, safe_halt) after the decision. If you must notify an approver, fire that notification from route_to_approver as an idempotent, keyed operation, or from a post-interrupt node - never as a bare side effect above the interrupt line.

Static interrupts are the wrong tool. LangGraph still exposes interrupt_before and interrupt_after compile-time flags, and older code uses NodeInterrupt. The documentation is now explicit that static interrupts are for debugging breakpoints and not recommended for human-in-the-loop; the dynamic interrupt() function plus Command(resume=...) is the current primitive (LangGraph 1.2.x, June 2026). If you are gating on a compile-time flag, you cannot carry per-action context - the tier, the approver, the expiry - into the pause, which is exactly the information the membrane needs.

Tier assignment is the part you will get wrong. The permeability key is only as good as the reversibility judgments fed into it. The dangerous failure is misclassifying an irreversible action as reversible and dropping it from Tier 3 to Tier 1, where it auto-executes. Default unknown actions to the most restrictive tier and require an explicit, reviewed declaration to lower them. Reversibility is not always obvious: an action can be technically reversible (you can delete the email you sent) but practically irreversible (the recipient already read it). When in doubt, classify on practical, not technical, reversibility. This connects to a theme from a sibling piece - authority does not compose; an agent that can take ten individually-Tier-1 actions may produce a Tier-3 outcome in aggregate, and the membrane on single actions will not catch it.

Some actions have no safe absent-human default - and the membrane has to admit that. The pattern lowers the volume reaching a synchronous gate; it does not promise every action has a safe non-human fallback. A legally mandated sign-off - a regulated transfer above a threshold, a change-managed production deploy, an export-controlled action - can have no acceptable timeout behavior at all: proceeding without a human is non-compliant, and denying forever fails the business. For those, the absent-human default is neither proceed nor deny but escalate indefinitely by policy - the run parks and re-pages a widening on-call chain until a human acts. That is the one case where a Tier 3 checkpoint legitimately blocks with no timeout-to-deny, and it is the naive blocking gate, used deliberately and narrowly. The membrane's contribution there is smaller but real: it keeps the reversible majority off that queue, so the mandated approvals are the only thing in it - the difference between a reviewer who reads them and one too buried to.

The membrane is a containment boundary, like a circuit breaker. This pattern is the oversight sibling of the resilience patterns in Fault Isolation and Circuit Breaking: both exist to stop a local event from becoming a system-wide failure. There, the fault is a provider outage and the containment is a classified retry policy; here, the fault is an unsafe action and the containment is a risk-classified approval. And both depend on the durable state model from State Architecture for Agent Networks - a parked Tier 2 checkpoint is only safe to wait on because the checkpointer makes its state survive partial failure. The seams this oversight rides on are the same agent-to-agent contracts described in Multi-Agent Topology Patterns, and the boundary-hardening instinct is the same one behind Designing Secure MCP Servers.

The control flow, end to end:

mermaid
flowchart TD
    A[Agent proposes action] --> K{Permeability key<br/>reversibility x blast radius}
    K -->|Tier 0: reversible, low blast| T0[Auto-pass, logged]
    K -->|Tier 1: costly but reversible| T1[Proceed now]
    K -->|Tier 2: slow / hard to reverse| W[interrupt: park run,<br/>wait for approver]
    K -->|Tier 3: irreversible / high blast| W
    W --> D{Resume signal}
    D -->|approved| EX[Execute]
    D -->|rejected| AB[Abort]
    D -->|expired: absent-human default| DEF{Which tier?}
    DEF -->|Tier 2| SH[Safe-halt + escalate,<br/>state preserved]
    DEF -->|Tier 3| AB
    T1 -.->|out of band, never gates| REV[Async review queue]
    T0 --> EX
    T1 --> EX

    classDef ctx fill:#95A5A6,color:#FFFFFF,stroke:#7A8A8B;
    classDef dec fill:#7B68EE,color:#FFFFFF,stroke:#6858DE;
    classDef good fill:#6BCF7F,color:#2C2C2A,stroke:#56B86A;
    classDef alt fill:#FFA07A,color:#2C2C2A,stroke:#E08A66;
    classDef warn fill:#FFD93D,color:#2C2C2A,stroke:#E0C02E;
    classDef err fill:#E74C3C,color:#FFFFFF,stroke:#C0392B;
    classDef sec fill:#98D8C8,color:#2C2C2A,stroke:#7FC0B0;

    class A ctx;
    class K,D,DEF dec;
    class T0,EX good;
    class T1 alt;
    class REV sec;
    class W,SH warn;
    class AB err;

The diagram makes the asymmetry visible: most of the graph's width - Tier 0 and Tier 1 - never touches the interrupt node at all. Only the two right-hand tiers enter the human path, and every path that enters it has a defined exit, including the one where the human never shows up.

The decision guide: building a checkpoint that scales

Use this when you are about to wrap a human approval around an agent action.

  1. Score the action on two axes before anything else: is it practically reversible, and what is the blast radius if it is wrong? These two answers pick the tier. Do not start from "should a human approve this" - start from reversibility.
  2. Default to the most restrictive tier for any unclassified action. Lowering a tier requires an explicit, reviewed declaration. Misclassifying down is the failure that hurts; misclassifying up only costs latency.
  3. Never call a synchronous human gate on a reversible action. If it can be undone and the blast radius is low, it is Tier 0 or Tier 1. Auto-pass it or flag it for after-the-fact review. Spending human attention here is what creates the fatigue that breaks the gate everywhere else.
  4. Declare the absent-human default at the same moment you declare the approval. Three things, every gated checkpoint, no exceptions: who approves, the expiry window, the action on expiry. A checkpoint without an expiry policy is a latent hung run.
  5. Build the expiry sweeper before you ship the first interrupt. LangGraph will not time out a wait for you. If the sweeper does not exist, "async approval" means "wait forever" the first time someone is unavailable.
  6. Fail closed on irreversible, safe-halt on reversible. Tier 3 expiry denies. Tier 2 expiry preserves state and escalates. Never let an expiry proceed an irreversible action - that is autonomy wearing a safety costume.
  7. Keep all pre-interrupt code pure. Side effects before interrupt() run twice on resume. Push them to the no-interrupt tiers or to post-decision nodes.
  8. Watch the queue, not just the agents - and know that it lies about safety. Instrument approval-queue arrival rate against human service rate. The queue stalls when arrivals exceed the rate a human can physically click through; that is the throughput failure, and it is the one your dashboards catch. But oversight quality collapses far earlier, at an arrival rate well below physical saturation, because vigilance fails before capacity does - a reviewer clearing requests at, say, 80% of their physical click-through rate (the exact fraction is illustrative, not a measured constant) is already deep into rubber-stamp territory while the queue still looks healthy. The dangerous operating point is the gap between those two numbers: fast enough to keep up, too fast to actually judge. Size your tier thresholds to keep Tier 2/3 arrivals below the vigilance limit, not the throughput limit.

The reframe to carry out of here: the goal was never to put a human in front of every action. It was to be in a position to intervene on the actions that matter, and to have a defined, safe answer for every action when intervention does not come. That is a membrane. The wall only ever looked like safety.

References


Agentic AI

Systems Design

Follow for more technical deep dives on AI/ML systems, production engineering, and building real-world applications:


Comments