<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">

  <channel>

    <title>Han, Not Solo</title>

    <description>Han Lee&apos;s blog on machine learning engineering, compound AI systems, search and information retrieval, and recsys — exploring machine learning, LLM agents, and data science insights from startups to enterprises.
</description>

    <link>https://leehanchung.github.io</link>

    <atom:link href="https://leehanchung.github.io/feed.xml" rel="self" type="application/rss+xml" />

    

      <item>

        <title>Stop Being Permanent Underclass. Implement a Token Factory.</title>

        <description>

          Welcome to the Permanent Underclass. Here&apos;s Your Token Budget for the Month - 

          Technology, software, and financial industries are all operating in headless-chicken mode. The pendulum swung from tokenmaxxing or PIP’ed in Q1 2026 to the opposite extreme of token budgets and throttling AI usage today. Everyone is getting whiplash from reactionary policies instead of pursuing a forward-looking AI transformation.

Use lower-cost AI models!

Don’t have the AI model think as much!!

Don’t spend too many tokens doing non-critical work!!!

These budget-conscious directives are among the gravest mistakes companies can make in AI transformation. AI transformation is about moving an organization into the next era, not cost control. Meanwhile, the AI gods have descended from the frontiers of human intelligence and are expanding aggressively into every downstream vertical. And we are sending a bunch of poor human souls armed with a $200 monthly token budget to defend against armies of Claude and Codex agents with unlimited tokens.

Good luck winning that battle.


  The AI equivalent of guns, germs, and steel is tokens, talent, and data.


But what about costs?

Not everyone has the seemingly blank-check budgets of frontier AI companies. That is why every company should be implementing—not building—a token factory today.

A token factory is a centralized service where employees dispatch AI agents to complete tasks on their behalf by spending tokens. Each agent acts on behalf of a user, inheriting that user’s permissions to ensure compliant access to company systems and data. And this is not for engineers only. The tasks should include coding, operating, taking notes, authoring PowerPoint decks, redlining Word documents, conducting research, and completing almost any other productivity task or workflow.



This differs drastically from employees using local copies of Claude Desktop, ChatGPT, Microsoft Copilot, or equivalent apps. In that model, every user must configure the connector, choose the model, decide how much thinking to allow, and try whichever god-awful influencer prompt tricks appeared on LinkedIn that morning. All of this is cognitive overhead that users don’t need.

Agents inside a token factory are already equipped with the connectors, tools, context, and permissions required to complete the work. The supposedly complex decisions are abstracted away.

The user assigns a task. The agent completes it on the user’s behalf. That’s it.

Cost optimizations can then be managed centrally. The platform can route simpler tasks to lower-cost models, reserve expensive reasoning for harder work, implement proper prompt caching, terminate dead loops, and govern the cost of each completed task. Instead of another expensive policy U-turn, the company develops permanent productivity capacity with controlled cost.

The data collected—specifically, the traces and trajectories—can be used to optimize the system.

Implement, don’t build

Most companies should implement a token factory. Almost none should build one.

Be honest with your assessments. If your company does not already have a mature machine-learning infrastructure platform team operating recommender systems, search, or another ML product at scale with mature MLOps practices, buy a platform. Most companies ain’t Google, Netflix, Meta, or Spotify.

Token factories are complex data and AI operations. They are not pretty frontends used to fool non-technical executives into approving another budget. They require identity delegation, model routing, sandboxed execution, connector management, observability, budget controls, and, most importantly, data operations.

Several companies are already building pieces of this, including Cognition (Devin), Factory, OpenHands, and Glean. Anthropic’s Claude Managed Agents recently joined the party, although it currently does not support zero data retention (ZDR) or HIPAA coverage, making it unavailable for many enterprise customers.

In the right hands, the operational data collected by the factory becomes the foundation for continuous learning. Every task produces a trace with the user prompt, reasoning, tools used, failures, cost, whether the user accepted the output, and what finally worked. This data can establish evaluation suites, which can then improve model routing, tools, prompts, policies, and eventually the agents themselves and your company’s sovereign model, as Satya Nadella describes it. The factory will have higher utilization and throughput the more your organization uses it, while today’s disconnected desktop apps merely produce thousands of isolated chat histories.

Your AI strategy should not voluntarily classify your company as a permanent underclass

Reference


  Satya Nadella, “The Reverse Information Paradox”



        </description>

        <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/07/13/token-factory/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/07/13/token-factory/</guid>

      </item>

    

      <item>

        <title>Hidden Technical Debt of AI Systems: Agent Evaluation Infrastructure</title>

        <description>

          Experimentation-centric designs, not benchmark running scripts - 

          Most conversations about evals collapse into which SaaS tool to buy, which metrics to track, which LLM-as-a-judge prompt to slap on, or which single headline benchmark score to worship. SWE-bench percentage. Humanity’s Last Exam. ARC-AGI. Pokémon Red. Or most often, a directive from the top saying LGTM. These scores are useful, but they are not the evaluation infrastructure. They are the artifacts left behind by a much larger system: task definitions, runtimes, snapshots, trace capture, state diffs, verifiers, judge calibration, replay, release gates, and production feedback.

For agentic AI systems, evaluation infrastructure has to do two jobs: a control plane that decides what to run and whether the result should change what we ship, and a data plane that runs the agent and records what actually happened. The rest of this post is about building both, and why the data plane is the hard part.

This post is about evaluation infrastructure and monitoring: the underlying system for how we benchmark agents, develop models and systems in a scientific, experiment-driven manner, and monitor production services across post-training, applied AI development, and inference time. We will also touch on how to use this same system to continuously learn and update an agentic AI system.



This is the fourth post in the series. The first defined the RL environment as a five-tuple — tasks, harness, verifier, state, configuration; the second went down to the runtime and its sandbox primitives; the third covered the agent harness. Evaluation infrastructure sits across all of them, and it is the most durable layer — the one that persists across model updates, harness rewrites, and runtime refactors.

And it is not about how to “run a benchmark.”

Rollouts

Rollouts is the core artifact of an Agentic AI system. Give an agent a task, the agent acts on an environment using tools, skills, sub-agents, memory, dynamic workflows, stopping criteria, and whatever heavy or thin harness wrapped around the model. The bounded attempt from initial state to terminal condition is an episode. Running the agent through that episode is a rollout. Or a trajectory using reinforcement learning abstractions: states, actions, observations, rewards. The recorded observability artifact is a trace: messages, tool calls, etc.



This is different from traditional machine learning, where we have inputs and a set of golden labels, tests, or holdouts. Data in, prediction out, and a score that measures how model output deviates from expected output. Heck, this is even different from the chat assistant evaluation we had in early years of ChatGPT, where an evaluation is a set of input prompts, the output responses, and some labeled data or some arbitrary metrics.

For agentic AI systems, we not only care about the final response or artifact, we also care about how it got there. The systems became multi-step with evaluation alongside with it.

The final response is only one artifact produced by the agent and not the full picture. As an example, a coding agent may end with a patch that passes tests, but the trace might show that it deleted tests it couldnt pass, removed files unrelated to the task, leaked a token into a log, or spent a few dozen tool calls looping on the same failing command. A research agent may end with a polished report, but the trace shows it laundered a claim through a circular citation. A calendar agent may say it scheduled the meeting, but the state delta has the wrong timezone or duplicate invite.

This is all to say that agents complete tasks by changing the environment, so final-output-only evals are no longer sufficient. This is the structural reason agent evaluation infrastructure looks less like a metric library and more like an experimental control plane sitting on top of a data plane.

The data plane is where the agent acts: the model, harness, runtime, tools, memory, environment state, traces, outputs, logs, snapshots, and state deltas. The control plane governs those runs: task selection, experiment and perturbation design, ablations, replay policy, verifier selection, score aggregation, regression tracking, dashboards, and release gates.

\[\underbrace{T, C, G}_{\text{control plane}} \;\rightarrow\; \underbrace{M, A, R, S, \tau}_{\text{data plane}} \;\rightarrow\; \text{traces, state deltas} \;\rightarrow\; \text{ship / rollback / retrain / acquire data}\]

where:

  $T$ = task suites, task distributions, including verifiers, rubrics, judges, and human review
  $C$ = configuration, skills, tools, stopping rules, token budgets, etc, for benchmarking and experimentation.
  $G$ = reports, dashboards, regression tracking, and release gates
  $M$ = model
  $A$ = harness
  $R$ = runtime
  $S$ = initial state, checkpoints, memory, and state deltas
  $\tau$ = trace schema and trace store


The control plane turns each run into a normalized, auditable, replayable experiment record, and that record drives the decision: ship, rollback, retrain, change harness, fix runtime, curate more envs, or fixing data mixture. Scoring the final response is the easy part. The harder problem is building a data plane rich enough that the score is explainable, reproducible, and actionable.



Chat Eval Was a Spreadsheet. Agent Eval Is a System.

Evaluating a single-turn chat assistant is simple. You collect prompts, generate responses, and score each one against a reference answer, a preference model, or an LLM judge. One input, one output, one turn. The whole thing fits in a spreadsheet -— prompt, response, label, score -— and if the average goes up, the assistant got better. There is only conversation state which is already tracked as texts on the screen.

Agentic systems have units of work measured in an episodes that can run hundreds of steps or more. That’s quite more complex than simple prompt-and-response pair. The agent calls tools, reads files, writes to databases, spawns sub-agents, and edits its own memory. The failure mode can hide at any steps in the rollout, not just at the final response. Run the same task twice and you might get two different traces, because the agents are nondeterministic and the world moved underneath the agent. And the mistakes become bad actions like deleted file, unfaithful reasoning, leaked secret, deleted file, a wrong refund, a leaked secret.

Same task, but every axis of the evaluation got harder:


  
    
      Concern
      Single-turn chat eval
      Agentic system eval
    
  
  
    
      Unit of work
      Prompt and response
      Episode and trajectory
    
    
      Main artifact
      Output text
      Output, trace, state delta
    
    
      Failure modes
      The final answer
      Any step in the rollout
    
    
      Environment
      Static prompt set
      Mutable runtime, tools, and memory
    
    
      Verification
      Score the answer
      Score outcome, process, and state
    
    
      Reproducibility
      Seed and prompt
      Seed, state, runtime, tools, memory, clocks, APIs
    
    
      Cost
      Inference tokens
      Inference, tools, runtime, and human review
    
    
      Safety
      A bad answer
      A bad action
    
  


A single aggregated score provides very sparse reward signal for us to udnerstand the agentic system. Thus we have to slice the full environments to understand and debug the agent. These includes task type, difficulty, tool family, runtime, memory state, safety class, cost bucket, latency bucket, and failure mode.

And the same infrastructure serve the entire agentic development lifecycle. Training and data work leverage the configurations for experimentation to decide what to curate and which environments to fold into the data mixture. Post-training utilizes this to monitor traces for alignment and safety. Applied development leans on it to hill-climb the eval score. A production gate leans on it to catch regressions and confirm expected behavior before release. And once the agent is live, the same instrumentation can also serve for production monitoring.

This is also why the same system shows up under two names. The first post in this series defined an RL environment as a five-tuple of tasks, harness, verifier, state, and configuration. From an eval teams perspective its a eval harness. The verifier that gates a release can often be reused as, or translated into, the reward signal that shapes a policy. That is why the evaluation layer outlives any single model or training run.

A spreadsheet and a simple script is no longer enough. Even at the end we report one single number.

Evaluation and monitoring surfaces

There are five surfaces for evaluating agent behaviors and requires different treatments – output, trace, memory, environment, and mechanistic interpretability. The first four are extrinsicly observable surfaces. Mechanistic interpretability is a subset of AI research on methods for inspecting model internals. This framing ensures you to look beyond just the final output.



1. Output

The final output is still the obvious first surface. Did the agent complete the task? Did the code pass tests? Did the spreadsheet reconcile? Did the answer cite sources that actually support the claim? Did the generated financial report validate against the source numbers? Did the UI meet the acceptance criteria?

Evaluation of these typically splits into verifiable tasks, such as math and coding, and open-ended tasks, such as analysis, research, and writing. The rewards/verifier can ouptut into a single score for convenience. To make them useful for research and experimentation, its usually breaking down into a matrix or a rubrics of scores to provide more signals for us to make decisions.

2. Trace

An agent might take a few steps or a few thousand. You can model the jumps between states as a state transition matrix, but over a  ng run that is too manual to maintain and too coarse to be useful. Knowing how often the agent went from “search” to “read” to “write” tells you something, but not what the run looked like or whether the agent worked through the task sensibly.

Looking directly at the agent traces lets you ask process questions:


  Did the agent use the tools it was supposed to use?
  Did it use tools it should not have used?
  Did it retry a failing tool until the budget burned down?
  Did it inspect the right files before editing?
  Did it validate after making a change?
  Did it cite sources it opened?
  Did it follow prompt-injection instructions embedded in tool output?
  Did the tool use cohere with the stated reasoning, or did the agent say one thing and do another?


This is the surface the rest of the infrastructure has to capture, and capturing it is not the same as keeping a console log. A usable trace is a structured record of every step: the tool called, the arguments passed, the observation returned, the latency and cost, and the state delta the action produced. This is most often captured in OpenTelemetry-style spans. Without that structure the process questions above are unanswerable, and you are back to grading the final answer.

Traces should not be scored primarily on whether they follow one golden path. Golden paths are useful for protocol compliance, regression tests, and regulated workflows, but they are a weak measure of agent intelligence. The stronger target is process invariants metrics and questions like “no unsafe writes” or “no jumping straight to a conclusion.”

A polished final answer does not mean its faithful to the context. A tool call can be syntactically valid but semantically useless. A response can be high quality while the path that produced it is unsafe or excessive. These are different failures, and a serious eval cannot infer one from the others.

The canonical trace-level failure is the empty tool result hallucination. The user asks for a number. The agent calls the right-looking tool. The tool returns an empty list. The agent fabricates a plausible answer. To the user, the final answer looks fine. To a trace evaluator, the answer is unsupported by the only evidence the agent actually observed.



A minimal trace record should include: run id, task id, step index, model and harness version, prompt/config hash, tool name, arguments hash, observation hash, latency, cost, permission boundary, state delta pointer, checkpoint id, verifier result, and failure labels. Anything less becomes hard to replay, compare, or audit.

The trace is what lets you evaluate whether the agent retried, changed parameters, escalated uncertainty, or reward hacked.

3. Memory

Memory here is not one single thing. There is conversation context, scratchpads, progress files, AGENTS.md, CLAUDE.md, memory.md, soul.md, skills, project docs, retrieved snippets, vector-store entries, cached summaries, and any other state the agent can read or change dynamically after deployment.

That layer needs evaluation because memory pollution changes agent behavior silently.

An agent can record an unintentional user note into a memory file and pollute context into the future. It can summarize a task incorrectly and carry the error forward. It can overfit to one user’s preference and apply it globally. It can inject misaligned data into a skill. It can decide that a temporary workaround is permanent truth.

The analogy is the YouTube dark hole. A recommendation system watches one session, over-exploits the user’s revealed intent, and corners them into the same narrow distribution. Agent memory can do the same thing to work. One bad episode becomes a durable preference. A polluted memory narrows exploration, repeats mistakes, and makes the agent look more confident as it gets worse.

A serious eval system should eval and monitor memory reads and writes:


  What memory did the agent consult?
  What did it update?
  Was the update justified by the task or user preference?
  Did the memory change improve future rollouts or poison them?
  Can we replay the same task with memory disabled, stale memory, clean memory, and polluted memory?


If memory is part of the harness, it is part of the evaluation surface.

4. Environment

Many agent tasks are state-transition tasks, not conversational text generations.

The agent changes a repository, a database, a spreadsheet, a calendar, a ticket queue, a browser session, a filesystem, a feature flag, a log stream, or a deployment environment. The correct answer is both what the agent respond at the end AND the environment, state, and artifaacts changed in the right way.

So the unit the infrastructure has to capture is the state delta: files added or deleted, database rows updated, migrations applied, environment variables touched, logs emitted, git refs changed, secrets accessed, browser cookies persisted. That means snapshots and diffs at each step, not only at the end — a calendar agent that reports “scheduled the meeting” can still used the wrong timezone or created a duplicated invite. We can only find these out through state deltas.

Capturing state per step, rather than only at the terminal state, is also what makes failure attribution possible. Outcome-level eval tells you the task succeeded or failed; the sequence of state deltas tells you which step caused it. For long-horizon agents that is the difference between knowing the run failed and knowing where. How you then score a transition is a verifier question and the evaluation infra provides the data for it.

If your eval does not capture the state delta, it is not flexible enough to evaluate tasks that are stateful.

5. Mechanistic interpretability

Mechanistic interpretability is the field that tries to understand neural networks by inspecting internal computations: activations, circuits, features, causal pathways. Sparse autoencoders (SAE) take layer activations and learn interpretable latent features. Transcoders try to map internal transformations between layers. Cross-layer transcoders (CLT) push the idea further by looking at computational graphs and causal relationships across layers rather than treating one layer in isolation.

This is the brain-surgery way of evaluating and monitoring AI agents. However, these are not available to teams using proprietary APIs. If you are calling a frontier model through an endpoint, you do not get layer activations. You get outputs, tool calls, token usage, and maybe logprobs if the API exposes them. Mechanistic interpretability belongs in the toolbox of teams who has full stack control, not in the average product eval dashboard.

For most builders, the absence of this means your extrinsic trace and state instrumentation have to be comprehensive.

Experimentation, Not Benchmarking

A benchmark is a frozen environment. Everyone can run the same benchmark with the same setup and have reproducible results.

Evaluation infrastructure for agents should be designed to be experimentation-driven, because the thing you are evaluating is a coupled system: model, harness, tools, runtime, memory, task distribution, state, and configuration.

We should take the scientific method approach in doing evaluations. Treat every change as a hypothesis, i.e., a new model, a reworded prompt, an added tool, and treat each eval run as the controlled experiment that tests the hypothesis. Change one variable, hold the rest fixed, and decide whether the result is real. There are in general two types of experiment designs.

Perturbation tests hold the task fixed and vary the paths available to the agents, e.g., change the turn limit, modify skills.md, randomly fail a tool, add stale documentation that conflicts with the source code. Because the intended task is unchanged, a change in performance isolates a single thing — whether the agent learned the task or just memorized one golden path.

Ablation tests remove one component at a time from the system and measure the delta against the baseline., e.g., long-term memory, semantic search, browser access, sub-agents, agent skills. The delta from the ablation is then the component’s contribution, and it provides signals on where to invest. If removing memory barely moves the score, the memory layer is theater. If disabling the browser only hurts one slice of tasks, you have located exactly where the browser earns its keep.

The same perturbation discipline doubles as a guardrail test. Inject tool errors, increase response latency, revoke a permission, or shut a tool down mid-run, and watch whether the agent recovers or fails closed. An agent that only works when every tool is healthy is not robust.

Configuration is the experimental design, and good evaluation infrastructure keeps it modular so you can vary one factor without disturbing the others.

Decouple the task from the harness. What you measure are in the control plane (the dataset, the answer contract, the scorer), separate from how you run it in the data plane (the model, the tools, the harness, the sandbox). Otherwise a model change and a harness change land in the same number and you cannot tell which one moved it — the confound that ruins the experiment. Ai2’s olmo-eval makes this the central abstraction: the same task runs as a plain baseline or with tools and scaffolding without changing what it measures, so you can swap the runtime under a fixed benchmark instead of forking the benchmark every time the harness changes. It also keeps a lightweight path by default and only reaches for an isolated container when a task actually needs to run code the model wrote.

Treat the comparison as a measurement, not a readout. A 2.4-point move in an average can be a real improvement or pure noise; Simpson’s paradox applies, and the mean alone will not tell you which. Pair the runs and report the uncertainty with a confidence interval and a minimum detectable effect alongside every number, so we can discount a “gain” that sits inside the noise band. The useful question is not “did the number go up”; it is “which component did we change that caused which tasks flipped and in which direction.” All of these runs, including state, configs, should be stored so we can re-run the experiment months after for the same results.



The production version of this loop is incident capture. Sample traces, redact sensitive data, label failures, attach state deltas, and promote representative failures into the regression suite. Production monitoring should not live in a separate universe from offline evals; it should continuously update the task distribution that gates future releases.

Checkpoints, Branches, And Replay

Long-horizon agent evals need checkpointing for the same reason RL and game engines need state snapshots – re-running from the beginning is too expensive and hides the source of variance.

A checkpoint is a restorable snapshot of the environment plus enough execution metadata to continue or inspect the rollout. A filesystem snapshot alone is not enough. You also need the conversation state, retrieved memories, tool observations, model and harness version, random seeds if available, clocks, package versions, database state, network fixtures, and pending jobs.

Resume, replay, and branch are different.

Resume means continue the same attempt from a checkpoint under the same intended configuration. Replay means inspect or re-execute a known trajectory from a known state. Branch means start from the same checkpoint but change one factor: model, harness, memory, tool availability, budget, or verifier.

From one checkpoint, you should be able to branch alternate futures:


  
    
      Branch
      Question
    
  
  
    
      Same state, new model
      Did the model upgrade help or regress?
    
    
      Same model, new harness
      Did the harness change matter?
    
    
      Same task, memory disabled
      Is memory helping or poisoning?
    
    
      Same state, tool outage
      Does the agent recover gracefully?
    
    
      Same failure, replayed trace
      Was the failure deterministic?
    
    
      Same task, lower turn budget
      Are we paying for unnecessary actions?
    
  


This is the practical version of experience replay for agent systems. In RL, experience replay often means sampling stored transitions from a replay buffer during training. For evaluation and debugging, the idea is that we should be capturing experiences as reuseable data through trajectory replay or stateful replay.

The moment an agent mutates external state, checkpointing becomes a necessity to debug and experiment.

State Infrastructure Is Evaluation Infrastructure

In productivity tasks, agents live in filesystems and databases. They edit repos. They run migrations. They write documents. They update calendars. They change tickets. They leave logs behind.

That means evaluation infrastructure needs state infrastructure $S$.

For filesystems, you need git worktrees, copy-on-write directories, overlay filesystems, container layers, or VM snapshots. For databases, you need transaction snapshots, logical dumps, branchable databases, copy-on-write volumes, seeded fixtures, or per-branch schemas. For browser agents, you need profiles, cookies, local storage, network recordings, and DOM snapshots. For enterprise agents, you need branchable mock systems with access controls and side effects.

Experimentation branching must not share mutable state unless sharing is explicitly designed as part of the environmnet.

We can not ensure the integrity of our evals if they runs against the same database for an confounding compoenent. And an eval infrastructure that cannot restore the state to a checkpoint is logs, not eval infra. State is the part of evaluation infrastructure that traditional ML teams tend to underestimate. They are used to datasets. Agents need worlds. Agentic evaluation infra needs multiverses.

Evaluation Debt and Durable Infrastructure

Evaluation debt accumulates through convenience. Someone wrote a notebook, with a csv file for tasks set and judge prompts in a shared Excel file somewhere. The model is whatever the default API of the week behind some model router/gateway with an ever changing tools schema. The production runtime is “basically the same” as the eval runtime, failures are pasted into Slack as screenshots, and the dashboard shows a green line that nobody can trace back to the run that produced it. Everything runs, but together they make the evaluation unreproducible.

The bill comes due at the next model upgrade or customer complaints. The aggregate score improves and the dashboard stays green, yet customers report that the agent got worse, and the team cannot reproduce the regression because production ran with a different memory state, tool timeout, browser profile, and system prompt than the eval ever saw. Someone adds a few tasks to the golden set, the average ticks back up, and the real failure ships anyway. This is cargo cult evaluation - the number moved up and to the right because the measurement changed, not because the agent improved.

A durable evaluation layer is the system that resolves this with evidence rather than a manual dashboard inspection. It can identify which slice regressed and which trace pattern changed, attribute the cause to the model, harness, runtime, memory, verifier, or state, and distinguish the agent failing the task from the scorer failing the agent. It can replay the failure, promote it into the regression suite, and tie the outcome to a ship, rollback, retrain, or environment-fix decision.

Agents do not only need datasets. Agents need worlds. Evaluation infrastructure is how those worlds become measurable.

References


  Sculley, D., Holt, G., Golovin, D., Davydov, E., Phillips, T., Ebner, D., Chaudhary, V., Young, M., Crespo, J.-F., &amp;amp; Dennison, D. (2015). Hidden Technical Debt in Machine Learning Systems. NeurIPS. https://papers.nips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html
  Lee, H. (2026). A Taxonomy of RL Environments for LLM Agents. Han, Not Solo. https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/
  Lee, H. (2026). Hidden Technical Debt of AI Systems: Agent Runtime. Han, Not Solo. https://leehanchung.github.io/blogs/2026/04/24/hidden-technical-debt-agent-runtime/
  Lee, H. (2026). Hidden Technical Debt of AI Systems: Agent Harness. Han, Not Solo. https://leehanchung.github.io/blogs/2026/05/08/hidden-technical-debt-agent-harness/
  Allen Institute for AI. (2026). olmo-eval: An evaluation workbench for the model development loop. https://allenai.org/blog
  Sutton, R. S., &amp;amp; Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.). MIT Press.


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {Hidden Technical Debt of AI Systems: Agent Evaluation Infrastructure},
    year = {2026},
    month = {06},
    day = {13},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/06/13/hidden-technical-debt-agent-evaluation-infra/}
}



        </description>

        <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/06/13/hidden-technical-debt-agent-evaluation-infra/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/06/13/hidden-technical-debt-agent-evaluation-infra/</guid>

      </item>

    

      <item>

        <title>Data Aggregation Is Not a Moat</title>

        <description>

          AI agents compress the cost of collecting, cleaning, and operating public data. - 

          For years, a lot of data businesses were not really selling unique or proprietary data. They were selling the operational burden of collecting, cleaning, storing, analyzing, and packaging data into a useful downstream application.

That workflow looked roughly like this:


  
  Fig. 1. A typical data + AI workflow goes through ingestion, cleaning, and storage in a repository. More sophisticated companies mine the data for analytics and turn it into machine learning models and AI systems.


The moat was that every step in the pipeline was annoying and expensive to operate. You had to know which sources mattered. You had to get licenses. You had to get access. You had to deal with sessions, rate limits, anti-bot systems, and changing page structures. You had to parse messy HTML, clean noisy records, normalize schemas, store the data, refresh the pipeline, and then turn the output into something a customer could actually use.

AI agents compress this cost structure.

Instead of writing brittle crawler code against fixed page structures, a user can describe the workflow in plain language. The agent can choose sources, navigate through a browser, use permitted logged-in sessions, read pages semantically, clean noise, summarize the result, and package the output. The pipeline starts to move from a maintained software system to an on-demand user workflow.

This is the old Semantic Web and web-agent idea coming back through a different path.

In the 1990s, the hope was that the web would become machine-readable through standards, metadata, ontologies, and cooperative publishing. That mostly failed at web scale because the incentives were wrong. AI agents route around that failure. They do not need every website to expose perfect semantic markup. They can interpret messy human-facing interfaces directly.

AI breaks data moats.

A real proprietary data moat still matters. Unique first-party transactions, exclusive rights, private telemetry, regulated records, and high-quality feedback loops unavailable to others remain defensible.

But most “data moats” are not that. They are collections of public, semi-public, or non-exclusive data wrapped in operational competence. Their defensibility came from the cost of aggregation, not from ownership of the underlying facts.

When the aggregation cost collapses, the moat collapses with it.

If a motivated user can ask an agent to recreate a useful slice of the dataset on demand, the static database becomes less valuable. If the agent can perform the research workflow directly, the dashboard becomes less valuable. If the agent can refresh the result every morning as a scheduled task, the monitoring service becomes less valuable.

The value shifts upward.

The defensible layer is no longer “we collected the public and private data.” It is trust, provenance, permissioning, workflow integration, evaluation, compliance, and, most importantly, the AI/ML models and systems built on top of those data assets.

The important question is no longer: who has the biggest pile of aggregated data? It is: who can produce a decision-quality answer that is current, verified, auditable, and integrated into the user’s work?

Data aggregation is not going away. Search-scale crawling, archival indexing, compliance-grade records, and high-volume pipelines still need serious infrastructure.

But the long tail of crawler-backed products is about to get repriced.

OpenAI and Anthropic are the highest-value version of the same pipeline.

OpenAI publicly documents separate crawlers for different jobs: OAI-SearchBot for search, ChatGPT-User for user-directed browsing, and GPTBot for web content that may be used to train generative AI foundation models. OpenAI also says its foundation models are developed from a mix of public internet information, data accessed through partnerships, and data provided or generated by users, human trainers, and researchers.

Anthropic has the same pattern. It documents ClaudeBot as a crawler that collects public web content that could contribute to model training, alongside separate agents for user-directed retrieval and search. Anthropic’s privacy materials also describe training data sources that include publicly available internet information, commercially obtained datasets, and data provided by users or crowd workers, while stating that it does not bypass password-protected pages or CAPTCHA controls.

The important business point is not that these labs aggregate data. Everyone in the data stack has done some version of aggregation. The important point is where the economic value lands.

They do not stop at ingestion. They clean, filter, transform, evaluate, post-train, and compress the data into model weights. Then they expose the result through products, APIs, coding agents, search, enterprise workflows, and developer platforms.

That is the full value chain: public, licensed, and user-provided data goes in; intelligence-as-a-service comes out. The model captures far more of the economic value than the raw dataset ever could.

The old business sold a maintained pile of gathered data. The new workflow gives the user an agent that gathers, reasons, and acts when the need appears.

Dataset as product is being compressed into an on-demand workflow that turns raw information into action.


        </description>

        <pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/05/10/data-aggregation-is-not-a-moat/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/05/10/data-aggregation-is-not-a-moat/</guid>

      </item>

    

      <item>

        <title>Hidden Technical Debt of AI Systems: Agent Harness</title>

        <description>

          Whatever you bolt onto the model today is what the next model will eat. - 

          If you have been building agentic products in the last twelve months, you have been writing harness code: system prompts, tool wrappers, planner-executor loops, retry policies, context compaction strategies, allowlists for which tools an agent can call from which surface, judges for when to stop, and fallbacks for when the model wanders. Even drawing workflows in no-code or low-code tools like n8n is harness work. Every team has built some of this. The good teams have built a lot of it. The bitter part is that almost all of it is going to dissolve into the next generation of models, and the teams who treat their harness as a permanent product surface are going to spend a year ripping it out.

In the previous post about hidden technical debts of AI systems, we went through designs of agent runtimes – where AI agents work – and the requirements of experimentation/evaluation and providing runtimes for agents to work in production. This post is about the AI agent itself, the ongoing research and discussions of this abstraction, and it being the technical debt that most are not aware and budgeting for.



This is the third installment of a small series. The first post defined the RL environment as a five-tuple $(T, H, V, S, C)$ — tasks, harness, verifier, state, configuration — and pointed at the harness as the thing that controls how the model interacts with everything else. The second post zoomed out to the runtime and the sandbox primitives underneath. This post zooms back in on $H$ and asks the question that has been sitting unasked across all the harness-engineering blog posts of the last six months: which parts of this thing are load-bearing, and which parts are just the structure we needed at this particular level of model capability?

Agent Harness

We define agent harness as the orchestration layer that sits between the model and the environments that the model is operating in. This includes system prompts or prompts, basic sets of tools such as terminal access (bash), filesystem access (read, write), and potentially MCP client to access MCP servers that could contain tools. An agent is then a harness + a foundation model.

An useful analogy is treating AI as the next generation of computing. The core intelligence is contained in the model, or the CPU in a computer. And the model by it self is not useful without the operating system to orchestrate instructions and tools. The harness is the operating system. It provides interrupts and interfaces to outside world, manages different processes and threads, and manages memory (model context) to provide the end user the illusion of infinite memory and resources.



Diving in deeper with a more grainular decomposition. A harness can be an union of:


  System prompt and persona — the standing instructions that bias the model’s behavior across every turn.
  Tool surface — the set of callable functions exposed to the model, and the schemas, descriptions, and examples that teach it how to use them.
  Rollout protocol — single-turn, multi-turn, ReAct, plan-and-execute, deep-research, multi-agent. The shape of the loop the model runs inside.
  Context manager — what gets carried across turns, what gets compacted, what gets summarized, what gets dropped.
  Memory — short-term scratchpads, mid-term progress files, long-term retrievable stores.
  Sub-agent topology — orchestrator, workers, judges, sub-skills, hand-off protocols.
  Guardrails and gates — input filters, output filters, action gates, allowlists, approval tiers, KL caps in training, behavioral red lines in production.
  Verifiers and judges — the things that decide whether a step succeeded, whether a plan should continue, whether the model should stop.
  Observability — traces, replay, eval hooks, the seams the human can grab to understand what happened.


Not all harnesses are built equal; some are very minimalistic, e.g., pi while some are full featured, e.g., Claude Code. Some focuses on local personal agents, e.g., OpenClaw, some on memory, i.e., Letta code, some on recursive long-context inference, e.g., Recursive Language Models, and some on massive multi-agent (MMA) collaboration, e.g. Gas Town. As the operating system layer of AI and agentic systems, this is exactly the same as the many flavors of linux distributions for all different imaginable use cases.

Other definitions of agent harness

Naming things is the hardest problem in computer science, and the definition of agent harnesses is no different. The abstraction is constantly evolving and companies have put out vairous definitions of agent harness. These definitions can be in two broad categories - one of which we mentioned above as the research and applied science focused. The other category is user centric definition.

Let’s use F1 racing team as an analogy. AI labs and research scientists/engineers build the engine, the agentic model that powers the car. And you need a mechanics team to build the harness and the shell of the car. And that is not enough, because you need to evaluate and optimize the race car so you actually have a chance to win the race. And a team of mechanics to opearate the system, the machine learning system engineers.

Now the star of the show. The driver. It is the user of the agentic model. The drivers might need to customize the car, his racing suit, shoes, helmets, and put differnt sponsorship stickers on his helmet, his lucky charms, etc. These are the second type of harness.

Birgitta Böckeler’s harness-engineering article draws a clean distinction. There is an inner harness that the model’s builder ships — Anthropic’s Claude Agent SDK, Cursor’s Auto, Codex’s app server. These are what we discussed previously. And there is an outer harness that the user assembles on top — AGENTS.md, MCP servers, custom skills, organization-specific code review agents. Both are harness. They evolve on different clocks and they accumulate different debt.

The OpenAI team described their internal harness work by saying that their hardest challenges had become “designing environments, feedback loops, and control systems” rather than writing application code. Anthropic’s effective harnesses for long-running agents post is a worked example: a two-prompt harness with an initializer agent that builds an init.sh, a claude-progress.txt, a structured feature list as JSON, and a coding agent that picks up the next failing feature, commits, updates the progress file, and stops. The harness is not the model. The harness is also not just plumbing. It is a deliberately designed feedback loop that turns one model call into useful work over a horizon longer than any single context window.

From research to production

The most under-discussed property of the harness is that the production harness and the training harness are not the same artifact, and increasingly should not be.

In production, the harness is a constraint surface. The agent is acting on behalf of a user, against real systems, with real consequences. You want a tight allowlist of tools, scoped credentials, approval tiers for write actions, input and output filters on prompt injection, an idempotent retry policy, a maximum runtime, an audit log, a kill switch. Ashpreet Bedi’s systems-engineering post is right that read-only access is a tool configuration, not a system-prompt instruction. Anthropic’s harness-design notes and OpenAI’s harness-engineering post are right that environment design is now a primary engineering activity. In production, the right defaults look like a careful deployment of a powerful but fundamentally untrusted process: principle of least privilege, deny by default, observe everything.

In training and research, the harness is an exploration surface. The model is generating trajectories that the optimizer will use to shape its policy. If you allowlist tools, you are pre-deciding what the model is allowed to learn to use. If you wrap raw APIs, you are pre-deciding the abstraction the model will be rewarded for using. If you constrain the action space, you starve the optimizer of the very signal it needs to discover better strategies. The training harness should be wide where the production harness is narrow.

This is also the structural reason first-party harnesses outperform third-party ones on the same model. When a lab post-trains a model, it does so inside its harness — its tool schemas, its rollout protocol, its system-prompt conventions, its context layout, its stop conditions. The policy is shaped against that specific surface. The capability lives in the weights, but the way those weights get invoked is part of the training distribution. Drop the same model into a third-party harness with different tool descriptions, a different loop shape, or a different memory layout and you are running it out-of-distribution: the model is being asked to act through an interface it was never optimized against. This is why Claude inside Claude Code feels different from Claude inside a generic ReAct wrapper, and why Codex with GPT outperforms a hand-rolled scaffold on the same model. The harness is part of the contract the model was trained under which makes first party harnesses preferrable. Empirical work showed that the same model evaluated in its first party harness (1ph) versus an off-the-shelf third-party harness (3ph) shows a measurable benchmark gap, with the first-party harness consistently on top for this specific benchmark.


  
  
    First party harness beats third party harness.
    posttrain bench.
  


That said, the first-party advantage is not a law. A third-party harness that invests heavily in a dimension the first-party harness underweights can claim the gap back. Letta Code is the cleanest current example. On Opus 4.5, Letta Code scores 59.1% versus Claude Code’s 41.6% on their reported benchmark — a third-party harness beating the first-party one on its own model, decisively. The mechanism is exactly the one the asymmetry argument predicts: Claude Code is thin on durable memory by design, Letta is built around a memory substrate, and the benchmark rewards memory. On GPT 5.1 Codex and Gemini 3, where the first-party harnesses are stronger overall, Letta lands within a few points but does not lead. The takeaway is not that first-party always wins or always loses — it is that the harness is load-bearing, and a third-party harness with a deliberate axis of investment can outperform a first-party harness that neglects that axis.


  
  
    Third-party harness can beat first-party when investment axes diverge.
    Letta Code outperforms Claude Code on Opus 4.5 by exploiting a memory layer the first-party harness does not prioritize. Source: letta.com/blog/letta-code.
  


The asymmetry, side by side:


  
    
      Dimension
      Training / Research Harness
      Production Harness
    
  
  
    
      Action space
      Maximal — let the model try anything that could plausibly be useful
      Minimal — explicit allowlist, deny by default
    
    
      Tools
      Raw, low-level, easy to extend; agent may write its own
      Wrapped, scoped, versioned, schema-validated
    
    
      Failures
      Welcome — failure is signal for the optimizer
      Suppressed — fail closed, retry, page someone
    
    
      Network
      Often offline or recorded for determinism; may include adversarial perturbations
      Live, with strict egress policies
    
    
      Guardrails
      KL caps, reward shaping, curriculum gates, anti-reward-hacking detectors
      RBAC, JWT scoping, action gates, output filters
    
    
      Verifier
      Programmatic, scaled, often noisy on purpose
      Deterministic where possible, human-in-the-loop where it matters
    
    
      State
      Forkable, snapshottable, replayable
      Durable, per-user, auditable
    
    
      Cost model
      Many cheap rollouts; tail behavior matters
      Few expensive sessions; latency and reliability matter
    
    
      What “good” means
      The policy improves on a held-out distribution
      The user’s task succeeds without an incident
    
  


Two specific consequences worth stating directly.

The training harness should not be a stripped-down production harness. This is path of least resistance is exactly wrong — copy the production stack, turn off the auth checks, point it at a mock database. A training harness that inherits production’s allowlists that cannot teach the model anything outside that allowlist. Microsoft’s recent post on instability in production-scale agent RL shows this point technically. Tool-conditioned contexts in long-running RL produce heavy-tailed importance-weight distributions that destabilize on-policy updates, and the fix is in the training harness, not in the production guardrails. This include KL caps, slice-by-mode diagnostics, failure-aware curricula. The training harness is itself an object of careful engineering, distinct from production.

The production harness should not be a deployed training harness. This is the more common mistake. A team builds an open-ended research environment to explore capabilities, gets impressive demos, then ships the demo. The agent acts on real customer data with no allowlist. The first time a prompt-injection payload flows through a tool output, the agent dutifully exfiltrates secrets because nothing in the harness told it not to. The training harness was wide on purpose; the production harness has to be narrow on purpose. They are different artifacts.

The bridge between research and production is the evaluation and alignment team; an evaluation harness that mirrors production tightly enough to catch behavioral regressions, run by the same team that owns the production prompts and tools. Leading AI companies structured teams that catches “did the agent changes behavior” lives between research and product, owns the system-prompt pipeline, and is graded on parity. It is a third artifact whose job is to make sure that their agents do not diverge in behaviors between harness releases and model updates and to ensure research and production alignment.

Aligning From the Inside Out, Not Shackling From the Outside

The deeper reason the production-vs-training asymmetry matters is alignment.

When you build a production harness, you are using software engineering to constrain model behavior from the outside. Allowlists, action gates, output filters, kill switches. These are necessary and they are not where alignment actually comes from. Each one of them is a fence around behavior that has not been shaped, and a sufficiently capable agent will eventually find a configuration where the fence is wrong — a tool that should have been blocked but was not, an output filter that misclassifies, an allowlist that was correct on Monday and obsolete on Wednesday because the product changed.

When you build a training harness, you are doing something different. You are letting the model explore the action space, observing what behaviors emerge, and shaping them with rewards. If the model learns to call a destructive tool inappropriately, the answer is not to add a software guardrail; it is to penalize the trajectory and let the policy update. The fence moves from outside the model to inside the model. This is alignment from the inside out. It is also the only kind of alignment that scales with capability, because every external fence has a fixed cleverness budget and the model’s intelligence is growing faster than software gymnastics.

Junyang Lin’s framing described this phenomenon. In the agent era the competitive edge moves from training algorithms to environments. Better environment produce models with better internal policies. Models with better internal policies need fewer external scaffolds in production. The teams that invest in the training harness are buying down their production-harness debt, two or three model generations out.

The two failure modes here are symmetrical and worth naming.

Over-shackling in training. A team imports its production allowlist into training “for safety,” runs RL, and produces a model that is well-behaved inside that allowlist and useless outside it. The model never learned to recover from a tool error because the production harness retried for it. It never learned to choose between two competing tools because the production harness routed for it. It never learned to stop because the production harness timed out for it. Worth being explicit: the production guardrails were doing the agent’s thinking, and the policy never had to grow that thinking.

Under-fencing in production. The opposite team trains a wide-open agent, watches it solve impressive tasks in development, and ships it with the same open harness against real systems. The agent is capable and unaligned at the surface. Prompt injection lands. Sensitive operations execute. The team patches with a stack of post-hoc filters, each one a piece of software engineering trying to substitute for shaping that should have happened in training.

The right shape, stated as a rule: the harness should be widest where the model is being trained, and narrowest where the model is being deployed, and the gap between them should be a deliberate, audited engineering artifact rather than an accident.

As model gets smarter, structure matters less

The progress of the last three years is the amount of hand-designed hard-coded orchestration logic in the harness retreating slowly into the model.

In 2023 we built RAG pipelines because models had small context windows and weak retrieval, and the harness held the entire memory layer — chunkers, embedders, vector stores, rerankers, query rewriters, citation checkers. The model was a passive consumer at the end of a conveyor belt. Most of the engineering effort was in the conveyor belt.

In 2024 we built workflows because models could not reliably call tools in a loop. Lance Martin’s account of building open-deep-research is the clearest version of this story I have read. The first version was an orchestrator-worker workflow where an LLM call decomposed a research request into report sections, parallel workers researched and wrote each section, and a final step concatenated the output. There was no tool calling because tool calling was unreliable. There was no flexible plan because flexible planning was unreliable. The harness was the structure that let a 2024 model do something useful, by working around its 2024 limitations.

By late 2024 and early 2025 the model could call tools. In winter 2024, MCP had given the ecosystem a shared interface for tools. By early 2025, Claude 3.7 and the o-series and DeepSeek-R1 had made interleaved reasoning and tool use part of the model’s native behavior. Junyang Lin from the Qwen team labelled the shift cleanly: the era of reasoning thinking, where the model thought longer in isolation, is being replaced by the era of agentic thinking, where the model thinks in order to act, observes the result, and revises. The model now decides when to plan, when to call a tool, when to stop, when to ask. Each of those decisions used to be a piece of code in the harness.



Lance’s second pass on open-deep-research is the lesson. He moved to a multi-agent system, kept the assumption that each sub-agent would write its own report section in parallel, and ran straight into the failure mode Walden Yan at Cognition warned about: sub-agents do not communicate well, parallel writes produce disjoint reports. The structure he had added in 2024 to compensate for unreliable tools was still there in 2025 even though the underlying problem had gone away. He removed it. Writing moved to a single final step. The system improved.

Hyung Won Chung from Meta puts the pattern in one line:


  add structure for the level of compute you have, then remove it, because the structure becomes the bottleneck for the next level of compute.


He was talking about model architectures, not agent harnesses, but it applies here unchanged. The chunked retriever was right for 2023’s context windows. The orchestrator-worker workflow was right for 2024’s tool-calling reliability. Each generation of model exposes the structure of the previous generation as overhead.

The Bitter Lesson for agent harness

The original Bitter Lesson is Rich Sutton’s argument that, across seventy years of AI research, methods that leverage general computation have always eventually beaten methods that encode human cleverness about how a problem should be solved. SIFT lost to ConvNets. Rule-based parsers lost to neural ones. Hand-crafted chess heuristics lost to AlphaZero. The pattern is not that engineering is useless; it is that the engineering shifts. Useful engineering moves to the substrate that scales. Engineering that encodes assumptions about what the model needs gets eaten by the next round of model improvements.

We are now watching this happen to the agent harness in real time. A few examples worth being specific about.

No-code workflow builders are dissolving. The 2024 canvas tools, such as n8n its peers, sold non-engineers the illusion of robustness: a visual workflow you can see, version, and rerun. What they actually delivered was repeatability of steps, not quality of output. A semiconductor fab can run the same recipe a million times and still have yield problems; a deterministic process does not imply deterministic results the moment any node contains an LLM, and compounding error gets ugly fast past a handful of nodes. By 2026 a single long-horizon agent does what those canvases tried to assemble out of dozens of nodes, with the loop running inside the model rather than around it.



Tool wrappers are dissolving. In 2024 every team wrapped raw APIs in cleaner, more LLM-friendly tool schemas. By late 2025 the model could read OpenAPI specs. The argument Gregor Zunic at Browser Use is making is sharper still: do not wrap Chrome’s DevTools Protocol either, because the model has read ten thousand DOM bug threads and CDP examples and can write the helper it needs the moment it discovers the helper is missing. His ~600-line harness is a helpers.py the agent edits, a daemon.py keeping the websocket alive, a SKILL.md, and a run.py. When upload_file() is missing, the agent reads helpers.py, writes upload_file() using DOM.setFileInputFiles, and proceeds. The harness shrinks; the model fills the gap.

Planner-executor scaffolds are dissolving. In 2024 the standard pattern was an explicit planner LLM call producing a plan, an executor LLM call running each step, and a separate reflection LLM call updating the plan. By 2026 a single agentic-thinking model interleaves planning, action, and reflection inside its own trace. The decomposition is happening inside the model, not inside our Python.

Memory layers are dissolving. Long-running agent memory was, until recently, a complicated stack of vector stores, summarization passes, and selective retrieval. Anthropic’s long-running agent harness uses a JSON feature list, a progress file, and git log. That is intentional. Plain text in the working directory is something the model already knows how to read, write, and reason about; a custom memory abstraction is something we have to teach it about every release.

Multi-agent topologies are dissolving. Cognition’s “don’t build multi-agents” argument and Anthropic’s effective-harness post point in the same direction: the multi-agent architectures we built to compensate for short context windows and weak tool use look increasingly like overhead now that the underlying constraints have eased. Sub-agents will probably stick around for true parallelism and for context isolation, but the elaborate orchestrator-router-judge-critic graphs we drew on whiteboards in 2024 are not going to age well.

This is what makes the harness a Bitter Lesson moment. The harness is the structure we need for the level of model capability we have today. A well-engineered 2026 harness is a 2026 artifact. The teams that internalize this design their harness so the structure can come out as easily as it went in. The teams that do not are building load-bearing scaffolding around a model that is about to outgrow it. This is why modern evaluation frameworks like harbor framework and benchflow provides flexible interfaces to swap between different harnesses.

Three Optimization Surfaces

If the harness is a 2026 artifact, the next question is where to spend optimization budget. The layered framing that has aged best is: model + harness = agent, and agent + skills = product. This gives three nested surfaces, each with a different cost and iteration cadence.


  
    
      Surface
      Cost to change
      Iteration cadence
      Owned by
    
  
  
    
      Skills / prompts
      Cheap — text edits, no recompile
      Hourly to daily
      Product builders
    
    
      Harness
      Medium — code, ships with the binary
      Daily to weekly
      Research engineers / Applied AI
    
    
      Model
      Expensive — post-training compute
      Quarterly, lab-side
      Research engineers
    
  


The cheapest surface is skills and prompts. The most expensive is the model. The harness sits in the middle. It’s code, so it iterates faster than the model and slower than a prompt edit.

A growing line of research tries to automate the harness layer the way DSPy automated prompts. Meta-Harness-style outer-loop optimizers and AutoHarness-style agent-synthesized code harnesses share a structure: treat the harness as a search space, use the model as both subject and signal, and let an outer optimizer evolve tool wrappers, prompt scaffolds, judges, and rollout protocols. On a fixed task distribution these methods do work — the optimized harness beats hand-built defaults on the held-out slice it was tuned against.

Two caveats are worth being direct about.

First, these optimizations are local. The optimized harness is overfit to its training distribution. Move it to a neighboring task and it underperforms a generic harness; what was learned was not generalization but task-specific compensation for the model’s current weak spots. This is the Bitter Lesson failure mode one level up — structure that fits 2026 capability against 2026 tasks is exactly what the next model release dissolves.

Second, auto-optimized harnesses widen the train/prod gap. The lab post-trained the model in its harness. The auto-optimizer evolved yours against your tasks. The two surfaces now disagree about how the model should be invoked, and there is no human-readable audit trail for the disagreement because nobody wrote the resulting structure. You inherit train/prod skew with extra steps and less interpretability — a worse position than either the first-party harness or a deliberately thin hand-built one.

The opinionated direction, and the one the thin harness, fat skills ethos articulates cleanly: push the work onto the cheapest optimization surface. Keep the harness thin, with a small, deliberately under-specified set of primitives that mirrors what the lab post-trained against. Put domain expertise into skills, where iteration is fast, the artifact is human-readable, and the cost of being wrong is a text edit rather than a release. Let the lab own the model layer; that is the work they are uniquely positioned to do, and the layer where compute beats cleverness most decisively.

Automated harness optimization is a legitimate research direction. However, shipping an auto-optimized or bespoke harness into production is tech debt accumulation, because the speedup you get on today’s task distribution is being paid for in skew, opacity, and obsolescence on the next model release. A thin harness with fat skills loses a few benchmark points on niche tasks and wins everywhere the distribution shifts. And that happen most of the time if you are building a product rather than chasing SOTA benchmark leaderboard.

What the Harness Looks Like When the Model Is Smarter Than the Engineer

A useful exercise is to ask, for each piece of harness in your system, what happens if the model gets meaningfully smarter next quarter.


  The system prompt that says “always think step by step” — does anything happen if the model already does?
  The tool wrapper that converts a clean API into a “more LLM-friendly” interface — is the model now better at the clean API than at your wrapper?
  The orchestrator-router-judge graph that decomposes the task — does the next model just do this in one trace?
  The memory abstraction with embeddings and rerankers — is plain text in progress.md plus git log enough now?
  The output validator that catches malformed JSON — does the new model just produce valid JSON?


For each piece, ask one more question: when this piece becomes obsolete, how hard is it to remove? If the answer is “an hour,” you have an option. If the answer is “a week,” you have a debt.

This is the operational form of Hyung Won Chung’s rule. The structure is fine; the structure is necessary; the structure should be removable. Lance Martin puts the same point as a design principle: stick to low-level building blocks rather than high-level frameworks, because the building blocks let you re-shape the harness as the model shifts under you. The teams I have watched ship the most durable agent products in 2025 and 2026 are the teams that treat their harness like a 90-day artifact rather than a permanent product surface. They expect to delete most of it on a model release boundary, and they organize their code so they can.

The browser-use team’s self-healing harness is the limit case of this idea. Most of the harness is code the agent itself can edit at runtime, plus a SKILL.md telling it how. When a helper is missing, the agent writes the helper. When a helper is wrong, the agent fixes it. The harness becomes a starting point, not a frozen surface — the engineer’s job is to seed the substrate, not to anticipate every action the model will need.

What This Implies for the Next Two Years

We had very fat harnesses because models were weak in many ways. They could not use tools reliably, had tiny context windows, little reasoning, no interleaved planning, and short horizons. Every weakness bought a category of harness gymnastics that we had to do: wrappers, workflows, memory stacks, planner-executor graphs. All of those are now nearly resolved. The model ate the harness.

And it will continue to do so. Multi-agent workflows and systems will soon be trained into the model, just like workflows were. What lasts is not any specific production scaffold. What lasts are the training and evaluation data, environments, tasks, and infrastructure: the durable substrate that lets you rebuild harnesses as models change. The wide training harness, the narrow production harness, and the evaluation harness that connects them should be deliberately engineered, but not mistaken for permanent product surfaces.

Thus, thin harness, fat skills. Build the durable substrate like you mean to keep it. Build each production harness like you mean to replace it. Treat application-facing scaffolding as a 90-day artifact, and organize the codebase and system so you can throw it away on a model release without flinching. Your software engineering gymnastics will be made redundant by the next model.

The runtime is the bill nobody is budgeting for. The harness is the bill nobody is budgeting for correctly. Most teams ship it too fat, without thinking about the important data and evaluation infrastructures. The teams that flip that ratio are the ones whose agents still work when the next model lands.

References


  Sutton, R. (2019). The Bitter Lesson.
  Martin, L. (2025). Learning the Bitter Lesson. https://rlancemartin.github.io/2025/07/30/bitter_lesson/
  Chung, H. W. (2024). Don’t teach. Incentivize. https://youtu.be/orDKvo8h71o
  Anthropic. (2025). Effective harnesses for long-running agents. https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents
  Anthropic. (2024). Building effective agents. https://www.anthropic.com/engineering/building-effective-agents
  Anthropic. Claude 4 prompting guide — multi-context window workflows. https://docs.claude.com/en/docs/build-with-claude/prompt-engineering/claude-4-best-practices
  Lopopolo, R. (2026). Harness engineering: leveraging Codex in an agent-first world. OpenAI. https://openai.com/index/harness-engineering/
  LangChain. (2025). The anatomy of an agent harness. https://blog.langchain.com/the-anatomy-of-an-agent-harness/
  Yan, W. (2025). Don’t build multi-agents. Cognition. https://cognition.ai/blog/dont-build-multi-agents
  Cognition. (2026). What we learned building cloud agents. https://cognition.ai/blog/what-we-learned-building-cloud-agents
  Zunic, G. (2026). The Bitter Lesson of Agent Harnesses. Browser Use. https://browser-use.com/posts/bitter-lesson-agent-harnesses
  Zunic, G. (2025). The Bitter Lesson of Agent Frameworks. Browser Use. https://browser-use.com/posts/bitter-lesson-agent-frameworks
  Yutori. (2025). The bitter lesson for web agents. https://yutori.com/blog/the-bitter-lesson-for-web-agents
  Böckeler, B. (2026). Harness engineering for coding agent users. martinfowler.com. https://martinfowler.com/articles/harness-engineering.html
  Lin, J. (2026). From “Reasoning” Thinking to “Agentic” Thinking. https://x.com/JustinLin610/status/2037116325210829168
  Bedi, A. (2026). Systems Engineering: Building Agentic Software That Works. https://x.com/ashpreetbedi/status/2041568919085854847
  Microsoft. (2025). Diagnosing instability in production-scale agent reinforcement learning. https://devblogs.microsoft.com/engineering-at-microsoft/diagnosing-instability-in-production-scale-agent-rl/
  Post-training harness mismatch and benchmark performance. https://arxiv.org/abs/2603.08640v1
  Lee, Y., Nair, R., Zhang, Q., Lee, K., Khattab, O., &amp;amp; Finn, C. (2026). Meta-Harness: End-to-End Optimization of Model Harnesses. https://arxiv.org/abs/2603.28052
  Lou, X., Lázaro-Gredilla, M., Dedieu, A., Wendelken, C., Lehrach, W., &amp;amp; Murphy, K. P. (2026). AutoHarness: improving LLM agents by automatically synthesizing a code harness. https://arxiv.org/abs/2603.03329
  gbrain. Thin Harness, Fat Skills. https://github.com/garrytan/gbrain/blob/master/docs/ethos/THIN_HARNESS_FAT_SKILLS.md
  Anthropic. (2024). Model Context Protocol. https://modelcontextprotocol.io/
  Anthropic. (2025). Claude 3.7 Sonnet. https://www.anthropic.com/news/claude-3-7-sonnet
  Lee, H. (2026). A Taxonomy of RL Environments for LLM Agents. https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/
  Lee, H. (2026). Hidden Technical Debt of AI Systems: Agent Runtime. https://leehanchung.github.io/blogs/2026/04/24/hidden-technical-debt-agent-runtime/


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {Hidden Technical Debt of AI Systems: Agent Harness},
    year = {2026},
    month = {05},
    day = {08},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/05/08/hidden-technical-debt-agent-harness/}
}



        </description>

        <pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/05/08/hidden-technical-debt-agent-harness/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/05/08/hidden-technical-debt-agent-harness/</guid>

      </item>

    

      <item>

        <title>Don&apos;t Outsource Your Understanding</title>

        <description>

          Cognitive surrender is the blue pill of 2026. - 

          In April, a partner at Sullivan &amp;amp; Cromwell — the firm that advises OpenAI on the safe and ethical deployment of artificial intelligence — sent an apology letter to Chief Bankruptcy Judge Martin Glenn of the Southern District of New York. An emergency motion the firm had filed in the Prince Global Holdings Chapter 15 case turned out to be riddled with hallucinated citations. Embrassingly, these halulu, more than forty errors, listed across three pages of corrections was caught by the opposing counsel.

Sullivan &amp;amp; Cromwell has policies for this. Nobody followed them. Someone in the chain of associates and partners who normally protect the firm from filing nonsense looked at the brief, decided it was good enough, and signed it. Their Louis Litt did not check the citations. The firm is now writing a please-don’t-sanction-us letter to a federal judge in front of the entire bankruptcy bar.

This is not isolated. Damien Charlotin has been cataloging hallucinated court filings — over 1,300 globally, and more than $145,000 in sanctions in Q1 2026 alone. The pattern is the same every time: a lawyer used AI to do a task, did not verify the output, filed it, and the opposing party caught it.

Everyone knows AI is taking over the trivial work. What is new is that it is taking over the non-trivial work too. Software developers vibe-code their apps. Now they try to call it agentic engineering, but lets be honest, its vibed. Lawyers run research and draft arguments through AI and file the result. Academics generate papers. Doctors generate notes. This is mostly fine and mostly expected. Refusing to participate is the same kind of mistake as refusing to use computers in 1984 and using Google in 2004.

It’s all part of evolution of technology. The internet surfaced information so we didn’t have to drive to the library. Google ranked the information into ten blue links so we didn’t have to sift through library indices to find the books and read them ourselves. AI is the next compression of the same loop. Now instead of us reading the sources, it reads them for you and hands back the synthesis. Each step in this chain is a form of cognitive offloading, and each step has been net positive for human productivity. There is no honest version of this argument that says we should have stayed in the library.

What specifically are we outsourcing?

When you punch numbers into a calculator instead of doing them on paper, you are cognitively offloading. You decided the calculator does this subtask better than you do, you handed it the inputs, you got a result back. The load-bearing part is that you, as a human, have enough math knowledge and education to have a rough sense of what the answer out to be and checked the result. A sanity check. If you typed 67 × 420 and the calculator returned 4, you would notice.

What is happening in 2026 is something else. The lawyer who filed the hallucinated brief did not punch a calculation into a tool and check the answer against a sense of the right ballpark. They asked the tool to do the whole task, including the part where you would have noticed the answer was wrong. They outsourced both the work and the verification at the same time. Shaw and Nave at Wharton name this cognitive surrender. You don’t need a name to feel this. Anyone who has submitted or approved a pull request without reading it, or sent a Slack reply they did not write, or signed off on a deck whose numbers they did not recompute, knows the shape.

Cognitive offloading keeps the human in the loop for verification. And through verification we learn and understand. Cognitive surrender removes the human entirely. The work still ships. Nobody catches the errors until a downstream wildfire breaks out.

Sometimes we the consumers of media are the downstream party.

In April, Barron’s analyzed corporate earnings releases, news statements, and government filings from Fortune 500 companies and found that one sentence shape had spread quickly: “it’s not just X—it’s Y.” It’s not just a product, it’s a platform. It’s not just a partnership, it’s a transformation. It’s not just compliance, it’s confidence. The construction rose from about 50 mentions in 2023 to more than 200 in 2025. Cisco uses it. Accenture uses it. So do Workday, McKinsey, and Microsoft.



The reviewers, including general counsel, CFO, CEO, read them and signed them because they sounded right. But its not their voice. They surrendered to AI. But the readers can tell. And this just leaks credibility, slowly, one em-dash at a time.

A few weeks ago a post landed on r/vibecoding from a solo founder. Six months of Cursor and Lovable and Bolt. He built an app that worked, with real users generating real revenue. This black magic feels like non-technical people can finally be agile, vibe code their way to billion dollar startups. Until the project slows down to a grinding halt under the weight of the slop-field. A contractor opened the repo, went silent for two minutes, and said “wtf is this.” Yes, every feature shipped on time, but nobody thought about structure nor architecture. The coding agents keep adding slops on top of slops. A new file here, a separate function there, layer of slop on top of slop. The founder outsourced the work and the understanding. Eventually, the debt came due, and cognitive surrendered founder do not have enough understanding to fix the slop. Somebody else needed to fix it.



Same story, one floor up. Software engineers who never trained a model, never built an eval set, never measured drift, now think they “do AI” because they wrapped a prompt in an HTTP call. They chain eight LLM nodes inside an n8n workflow, wire it to a database, slap a Slack trigger on the front, and ship it. The drag-and-drop canvas looks clean. The arrows point forward. The green checkmarks fire. Underneath, it is slop. There is no eval set, no calibration, no precision-recall number, no falsifiable answer to “does this thing work” — only “did it run.” The eighth node hallucinates on Tuesdays and nobody catches it, because nobody set up the test that would catch it. Cognitive surrender by people whose job titles say they should know better. The slop now ships from the engineering org too.



ICLR is one of the two most prestigious machine-learning conferences in the world. In December 2025, GPTZero scanned 300 of the conference’s submissions for hallucinated references and found 50 with confirmed fabrications — fake author lists on real papers, attributions to coauthors who do not exist, citations to papers that were never written. Several of the flagged submissions had already received average reviewer scores of 8 out of 10, meaning three to five working researchers in the field had read each paper, scored it as accept-worthy, and not noticed that the citations didn’t resolve. ICLR responded by building an automated reference-checker into the review pipeline and desk-rejecting 779 submissions across the cycle for procedural and content violations. The conference’s own retrospective acknowledges the detector has a meaningful false-positive rate. In plain english, some authors who didn’t vibe their papers got swept up in the response to authors who vibed.

These are researchers writing papers about how large language models work, citing sources their own large language models invented, in submissions reviewed by other researchers writing papers about how large language models work. If anyone on Earth should know better, it is this group. They surrendered anyway.

This matters more with AI than it ever did with tools we invented in yesteryears. AI is fluent across every domain. A calculator is only confident about arithmetic, so nobody surrenders to it about anything else. Google returned a list of sources, which forced you to do the synthesis and therefore to keep some model of the answer. AI returns a finished synthesis in the voice of an expert, in any domain you ask, with no obvious surface signal of when it is right and when it isn’t. The fluency is the trap. It makes cognitive surrender feel like cognitive offloading. Same prompt, same result, same one-click experience, except you outsourced your understanding.

In 2002, Michael Crichton coined Gell-Mann Amnesia. You read a newspaper article about a subject you know deeply and immediately notice inaccuracies: wrong details, missing context, and a source who is not the right expert. You turn the page, read an article about a subject you do not know, and trust the newspaper again. The same thing happens with AI. You catch a model hallucinating about something you know cold. The next morning, you ask it about something unfamiliar and accept the answer. The model did not get smarter overnight. You simply lost the ability to check it.

The skill the next decade will reward is not using AI well. That’s the default and in a couple years that’s the floor. The skill that will separate the people who ship things that hold up from the people writing apology letters and the people whose papers get desk-rejected is staying in the cognitive loop, and being able to understand and verify the answer. Read the agent’s output. Walk one path through the code. Pull one of the cases the brief cites. Click through to one of the references in the related-work section. Rewrite the paragraph in your own cadence. This reconstruction is the work.

The S&amp;amp;C partner who signed the brief had access to every legal database in existence, an army of associates, and the institutional memory of a 150-year-old law firm. None of that protected him from a hallucinated citation, because none of that was in the loop at the moment of signature. Opposing counsel was. And opposing counsel had done the unfashionable thing of reading the cases.

Outsource everything else. Don’t outsource your understanding.

References


  Mystal, E. (2026). Sullivan &amp;amp; Cromwell Files Emergency ‘Please Don’t Sanction Us For All These AI Hallucinations’ Letter. Above the Law. https://abovethelaw.com/2026/04/sullivan-cromwell-files-emergency-please-dont-sanction-us-for-all-these-ai-hallucinations-letter/
  Bloomberg Law. (2026). Sullivan &amp;amp; Cromwell Apologizes to Judge for AI Hallucinations. https://news.bloomberglaw.com/business-and-practice/sullivan-cromwell-apologizes-to-judge-for-ai-hallucinations
  Volokh, E. (2026). AI Hallucinations in Filing by a Top Law Firm. Reason. https://reason.com/volokh/2026/04/21/ai-hallucinations-in-filing-by-a-top-law-firm/
  Lat, D. (2026). An AI Screw-Up By… Sullivan &amp;amp; Cromwell? Original Jurisdiction. https://davidlat.substack.com/p/sullivan-cromwell-ai-fail-screw-up-error-hallucination
  ComplianceHub. (2026). The 2026 Legal AI Reckoning: A Case-by-Case Breakdown of Every Major Hallucination Incident This Year. https://compliancehub.wiki/legal-ai-hallucination-reckoning-2026/
  Barron’s. (2026). AI Is Changing How Companies Talk to Shareholders. Here Is the Red Flag for Readers.
  ICLR. (2026). A Retrospective on the ICLR 2026 Review Process. ICLR Blog. https://blog.iclr.cc/2026/03/31/a-retrospective-on-the-iclr-2026-review-process/
  Shaw, S. D., &amp;amp; Nave, G. (2026). Thinking — Fast, Slow, and Artificial: How AI is Reshaping Human Reasoning and the Rise of Cognitive Surrender. SSRN Working Paper. https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6097646
  Risko, E. F., &amp;amp; Gilbert, S. J. (2016). Cognitive Offloading. Trends in Cognitive Sciences, 20(9), 676–688. https://www.sciencedirect.com/science/article/abs/pii/S1364661316300985
  Crichton, M. (2002). Why Speculate? International Leadership Forum.


@article{
    leehanchung_dont_outsource_understanding,
    author = {Lee, Hanchung},
    title = {Don&apos;t Outsource Your Understanding},
    year = {2026},
    month = {05},
    day = {01},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/05/01/dont-outsource-your-understanding/}
}



        </description>

        <pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/05/01/dont-outsource-your-understanding/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/05/01/dont-outsource-your-understanding/</guid>

      </item>

    

      <item>

        <title>Hidden Technical Debt of AI Systems: Agent Runtime</title>

        <description>

          Agents needs to live in a runtime. We need the runtime to research and develope agents. - 

          Eleven years ago, Sculley et al. drew the diagram everyone in MLOps has seen: a tiny black box labeled “ML Code” surrounded by a sprawl of much larger boxes — data collection, feature extraction, configuration, monitoring, serving infrastructure. The point of the diagram was that the model code is the smallest piece of a real ML system, and that everything else is where the technical debt accumulates.



The same diagram is being redrawn for agents. The agentic model call is the small box. The largest box to the right that is currently driving most of the spend and influencing how system architecture will be done in the futureincidents, is the agent runtime, or agent serving infrastructure.

The agent is not the model. The agent is the harness plus the model, running inside the runtime. And almost nobody is treating it that way.

What an Agent Runtime Actually Is

A agentic model by itself maps from text input to text output. Other modalities are in play, but the primary fabric is still text. An agent is what you get when you wrap the agentic model with harnesses so it can take actions, observe effects, feed those observation back into the next call. The execution environment where that happens is the runtime.

Concretely, an agent runtime is the union of:


  compute substrate — a container, microVM, or full VM where code runs.
  filesystem the agent can read and write, often with snapshot and rollback semantics.
  tools — shell, code interpreter, browser, file editor, MCP servers — exposed to the model as callable interfaces.
  network boundary that defines what the agent can reach and what can reach it.
  state model that decides what persists across turns, across episodes, and across users.
  lifecycle controller that starts, suspends, snapshots, resumes, and tears down environments.


In the taxonomy of RL environments for LLM agents, these are the $H$ (harness) and $S$ (state) components. In production, this is the part that decides whether your agent finishes a task in eight seconds or eight minutes, whether two users can share an environment safely, and whether a malicious prompt can read your secrets.

Most teams shipping agents today have not built this layer. They have rented it, glued it together from cloud primitives that were designed for a different workload, or simply not thought about it yet. That last group is the one accumulating the most debt.

Why Sandboxing Is Not Optional

Models hallucinate code. They run rm -rf. They paste credentials into curl commands. They follow instructions embedded in untrusted documents. They retry the same failing command in a tight loop and burn through a quota. None of these are exotic edge cases — they are the modal behavior of capable models when handed unrestricted tool access.

The sandbox is what stops these behaviors from becoming incidents. Four reasons it has to exist as a first-class layer rather than a hopeful disclaimer in the system prompt:

Isolation against the model’s mistakes. A coding agent that mounts your repo and has shell access can, and eventually will, delete the wrong directory. Filesystem isolation, copy-on-write snapshots, and per-session ephemerality turn destructive actions into recoverable ones.

Isolation against prompt injection. The agent reads tool outputs. Tool outputs are attacker-controlled the moment the agent visits a webpage, opens a PDF, or processes a customer email. A sandbox is the only thing standing between an injected instruction and your production database. This is the agent-systems version of the SQL injection lesson, and we are at roughly the 2003 stage of learning it.

Multi-tenancy at training scale. RL training spins up thousands of concurrent rollouts. Each rollout needs its own filesystem, its own process tree, its own network namespace. Without strong isolation primitives, one rollout’s flaky shell command takes down a neighbor’s training step.

Reproducibility. A sandbox you can snapshot is a sandbox you can replay. Replay is how you debug a six-hour agent trajectory without re-running the whole thing. It is also how you turn a production failure into a regression test.

A web app’s runtime can be sloppy because the user is the one driving and a refresh fixes most things. An agent’s runtime cannot, because the agent will keep going long after a human would have stopped to ask a question.

The Cognition team is direct about this in their post-mortem on building Devin’s cloud agent infrastructure: containerized agents share a kernel, and a single compromised session can reach every other container’s filesystem, credentials, and network connections. Because agents generate their own code, run arbitrary commands, and probe the environment in unpredictable ways, kernel-escape is not a theoretical risk — it is the working assumption. Their conclusion, after more than a year of hypervisor engineering, is the same one the broader infrastructure community has converged on for any untrusted-code workload: VM-level isolation, where each session gets its own kernel and there is no shared attack surface.

Manus team has famously built and snapped shotted all their agent runtimes in a restorable format so users can come back in the future to replay what agents have done.

The Isolation Primitive Stack

Most of the differentiation between sandbox vendors lives one layer down, in the isolation primitive they pick. There are five primitives in serious use, with very different trade-offs.


  
    
      Primitive
      Isolation model
      Cold start
      Workload fit
      Notable users
    
  
  
    
      Linux containers (runc, Podman)
      Shared host kernel, namespaces + cgroups + seccomp
      ~100ms
      Trusted code, internal CI
      Docker, Kubernetes default
    
    
      Firecracker
      KVM-based microVM, dedicated kernel per VM, ~5MB Rust VMM
      ~125ms boot, sub-second from snapshot
      Untrusted code at high density
      AWS Lambda, Fargate, E2B, Fly.io
    
    
      gVisor
      Userspace kernel intercepting syscalls; runc-compatible runtime
      Container-class
      Defense in depth where a microVM is overkill; GPU virtualization
      Google Cloud Run, App Engine, Modal
    
    
      Kata Containers
      Lightweight VM per pod, OCI-compatible
      Few hundred ms
      Multi-tenant Kubernetes
      Confidential Containers, some managed K8s
    
    
      V8 isolates
      Per-tenant JS heap inside a single process
      Sub-millisecond
      JavaScript-only, no arbitrary binaries
      Cloudflare Workers, Deno Deploy
    
  


A few things are worth saying out loud about this table.

Containers are not a sandbox for agent code. They are a packaging and resource-control mechanism. The shared kernel means a kernel exploit, a misconfigured capability, or a sloppy seccomp profile takes down the isolation boundary. It is not okay for an agent that may execute attacker-controlled instructions hidden in a web page.

Firecracker is the de facto industry primitive for this category. AWS open-sourced it in 2018 to power Lambda and Fargate, and almost all agent-sandbox startup runs on top of it, including E2B, Fly.io, Vercel Sandbox. It boots a stripped-down Linux kernel inside KVM in roughly 125 milliseconds and uses about five megabytes of memory for the VMM itself. The combination of strong isolation, fast boot, high density is what makes thousands of concurrent rollouts economically viable.

gVisor is the middle path. It runs a userspace kernel that intercepts and re-implements Linux syscalls, giving you stronger isolation than namespaces without paying the full cost of a hardware VM. The trade-off is that some syscalls are slower or unsupported, which matters for workloads that hit the kernel hard. Google uses it for Cloud Run and App Engine; it is the right pick when you want defense-in-depth on top of containers but cannot move to microVMs.

Kata Containers fit a specific niche. They are an OCI-compatible runtime that wraps each pod in a lightweight VM, which lets you run untrusted workloads on Kubernetes without rewriting the orchestration layer. The cost is that you inherit Kubernetes’ assumptions about pod lifetime, which do not match how agents actually behave.

V8 isolates are the wrong primitive for general agent workloads. They are extraordinary for JavaScript-only edge compute but an agent that needs to run arbitrary Python, install packages, drive a browser, or execute compiled binaries cannot live inside a V8 heap. Cloudflare’s own Sandbox product addresses this by adding container-class compute alongside isolates.

The picks higher up the stack inherit these trade-offs. When a sandbox vendor advertises “VM-level isolation” they almost always mean Firecracker. When they advertise “millisecond cold starts” without VM isolation, they usually mean V8 isolates with a different threat model. The vendor brochure abstracts the primitive away; the threat model and the workload do not.

The Startup Sandbox Landscape

A category of “sandbox-as-a-service” companies has emerged in the last two years, almost all of them building on top of the primitives above. Northflank’s roundup of Modal Sandboxes alternatives is a good market snapshot — the differentiation is in the developer ergonomics, the snapshot model, and the mix of tools that come pre-wired, not in the isolation layer underneath.


  
    
      Vendor
      Isolation
      Snapshot model
      Cold start
      Notable design choice
    
  
  
    
      Modal
      gVisor
      Filesystem diffs from base image
      Sub-second from snapshot
      GPU support
    
    
      E2B
      Firecracker microVM
      Full VM snapshot
      ~150ms
      Open-source SDK, language-agnostic, popular in the agent dev community
    
    
      Daytona
      Containers / VMs
      Forkable workspaces
      Few seconds
      Forked dev environments, OCI-compatible
    
    
      Northflank
      Containers, GPU-aware
      Persistent volumes
      Container-class
      GPU sandboxes for agents that train or run inference inside the loop
    
    
      Browserbase / Steel / Hyperbrowser
      Containerized browsers
      Session replay
      Seconds
      Browser-only runtimes for web agents — DOM, screenshots, CDP
    
    
      Cloudflare Workers Sandbox
      V8 isolates + containers
      Object snapshots
      Milliseconds
      Edge-first, shared-nothing model
    
    
      Vercel Sandbox
      Firecracker
      Snapshot from build
      Sub-second
      Tied to Vercel’s deploy and preview model
    
  


The reference Modal case study is Ramp Inspect: a background coding agent that now writes more than half of Ramp’s merged pull requests, with each session running in its own Modal sandbox containing Postgres, Redis, Temporal, RabbitMQ, a VS Code server, and a VNC stack with Chromium. Filesystem snapshots are refreshed every thirty minutes by a cron, so a new session is working on a prompt within seconds. The lesson buried in that architecture is that the agent’s productivity is bounded by the runtime’s startup time, not by the model’s tokens-per-second.

The browser-runtime category exists because driving Chromium is its own engineering problem — CDP, profiles, residential IPs, captcha handling, session persistence — and no general-purpose sandbox has it built in. Expect this to consolidate into the general-purpose vendors over the next eighteen months, the same way headless browsers absorbed into the major test frameworks.

It is also worth noting what nobody on this list does themselves. None of these vendors writes their own hypervisor. They all stand on top of the primitives in the previous section, which is what lets the category exist. The startups that have tried to build a complete stack — including the hypervisor — have ended up looking more like Cognition: years of engineering investment to get something that is finally indistinguishable from “we run on Firecracker with custom snapshot logic.”

The Hyperscaler Sandbox Landscape

The hyperscalers got to this market late and are still catching up.

AWS Bedrock AgentCore ships a Code Interpreter and a Browser Tool as managed runtimes for agents. The isolation is microVM-class, the integrations point at the rest of the AWS data plane, and the pricing model is per-session. However, it has major gaps relative to the startups, where the runtime has to be coded and shipped like a Lambda function, instead of having the flexibility to support heterogeneous workloads, e.g, different agent harness + model combinations. Not a major problem for production, but a major problem for research and development.

Azure Container Apps Dynamic Sessions uses Hyper-V isolation and advertises sub-second start times for code interpreter sessions. It is the most directly comparable hyperscaler offering to E2B and Modal in terms of intent. The integration story is strongest if you are already on Azure OpenAI and AKS. It also suffers from the flexibility to support heterogeneous workloads.

GCP Cloud Run Sandboxes is ahead of the pack with the usability similar to Modal, E2B, Daytona and the likes. It’s built on top of Cloud Run, which uses gVisor. Supports heterogeneous workloads.

Lambda, Fargate, App Runner, Cloud Run. It is tempting to use these as agent sandboxes because they are cheap and already in your account. They are designed for stateless, request-response workloads with strict execution-time limits and no native snapshot model. They will work for a demo. They will not work for an agent that runs for six hours, mutates a filesystem, and needs to fork mid-trajectory.

The hyperscaler offerings are converging on the right shape — microVM isolation, fast snapshots, managed tools — but the primitives underneath were built for web workloads. The startups had the advantage of building on top of those primitives without inheriting the legacy product surface area.

Experimentation and Production Want Different Runtimes

One of the major difference how AI engineering differes from traditional web development is the difference in development and production infrastructure requiremetns. Here’s the difference between your training cluster (if you are training models), experimentation and evaluation runs, vs production workload.


  
    
      Dimension
      Experimentation / Training
      Production / Serving
    
  
  
    
      Concurrency shape
      Thousands of parallel rollouts, bursty
      One session per user, steady-state
    
    
      Cold start tolerance
      Critical — 5s × 10k rollouts is real money
      Forgivable — users wait
    
    
      State model
      Fork, branch, replay, snapshot
      Durable, per-user, auditable
    
    
      Network policy
      Often offline or recorded for determinism
      Open internet, real APIs
    
    
      Failure model
      Drop the rollout, sample more
      Retry, degrade, page someone
    
    
      Observability
      Full trace, every token, replayable
      SLOs, error budgets, sampling
    
    
      Cost model
      Spot, preemptible, batch
      Reserved, predictable, latency-bound
    
    
      Determinism
      Required for reproducible runs
      Often counterproductive
    
    
      Lifetime
      Seconds to minutes
      Minutes to hours, or pinned for a session
    
  


A training rollout wants to start in 200 milliseconds, run for thirty seconds against a frozen snapshot of the world, get scored, and disappear. A production session wants to start in two seconds, hold state for a forty-minute conversation, talk to the live internet, and survive a transient failure without losing the user’s work.

The production side has a requirement that almost nobody surfaces in eval reports: the agent needs to survive the async gaps in real engineering work. Cognition’s experience report is the cleanest articulation of this — an agent opens a PR, waits on CI, responds to a review comment, reruns tests, pushes a follow-up commit. Between each step there are minutes, hours, sometimes days where the agent’s working state has to persist. A containerized agent can only survive these gaps by burning compute to stay alive, and if the container is rescheduled or times out the session is lost. Their solution was hypervisor-level snapshotting of the full machine state — memory, process tree, filesystem — so compute shuts down while the agent is idle and resumes exactly where it left off when a CI result arrives. Building that took longer than any other piece of infrastructure they had shipped to date.

Optimizing the same runtime for both training and production is how you end up with a system that is too slow for training and too brittle for production. Most teams discover this after the first time they try to “just deploy what we trained on” and find the latency budget eaten by a startup model that was fine when amortized over ten thousand rollouts and intolerable when paid by a single user staring at a spinner.

Dev/Prod Parity Is the Real Problem

In 2011, Twelve-Factor App told us to keep development, staging, and production as similar as possible. That advice was about reducing the gap between where the engineer types code and where the code runs. For agents, the gap that matters is between where the agent trained and experimented and where the agent runs.

An agent learns the runtime. Tool latencies, failure modes, shell quirks, filesystem layout, the exact way ls formats output, the way the browser resolves redirects. The model picks up all of it during training and bakes it into the policy, or the optimizer (RLM or GEPA) picks up all of it during training and bakes it into the harness or the prompts. Move these to a different runtime and the behavior shifts. Tools that were idempotent become flaky. Commands that were instant now block. Snapshots that existed disappear. The agent has no abstract concept of “shell”; it has a concept of “the shell I trained on.”

This is a new flavor of distributional shift. It is not the data shift the MLOps community has been worrying about for a decade. It is runtime shift, and it shows up as silent quality regressions that no eval catches because the eval runs in the training runtime.

There are three honest paths through this:

Co-locate train and prod on the same runtime. Pick one sandbox provider, run RL rollouts on it, run production sessions on it, accept the lock-in. This is what the most disciplined AI engineering teams are doing.

Define a runtime contract and enforce it on both sides. A small, versioned interface — “this is the shell, these are the tools, these are the latencies, these are the failure modes” — that you implement once on your training infrastructure and once on your production infrastructure. The contract is the abstraction; the sandbox underneath can vary. This is harder than it sounds because the contract has to cover not just the API surface but the timing and failure semantics that the agent has learned.

Train against production noise. Inject latency, errors, and tool failures during training so the agent’s policy is robust to runtime variance instead of dependent on a specific runtime. Step-DeepResearch reports tangible gains from injecting 5–10% tool errors during training. This is the runtime analog of dropout.

The wrong answer, the one most teams are unconsciously picking, is to pick a sandbox for production as a software engineering solution, and forget all about the requirements from AI and machine learning, then spend the next quarters chasing flakiness of agent performances.

The Bill That Is Coming Due

Sculley’s argument was that ML systems accumulate technical debt in places that traditional software engineering does not have language for — feedback loops, configuration sprawl, undeclared consumers, glue code. Agent systems inherit all of that and add a new line item: the runtime debt.

Runtime debt looks like this. A team picks the sandbox that was easiest to integrate at prototype time. Production traffic exposes a different mix of failure modes. The team patches around the gap with retries, longer timeouts, and prompt-engineered apologies. The agent’s behavior gets entangled with that runtime’s quirks with whack-a-mole without disciplined research and development. A year later, switching runtimes is a six-month migration because nobody can predict which behaviors will move.

The agent has to live on a runtime. The teams that internalize that early will spend the next two years compounding the advantage. The teams that do not will spend the next two years paying down a debt they did not know they were taking on.

References


  Sculley, D., Holt, G., Golovin, D., Davydov, E., Phillips, T., Ebner, D., Chaudhary, V., Young, M., Crespo, J.-F., &amp;amp; Dennison, D. (2015). Hidden Technical Debt in Machine Learning Systems. NeurIPS. https://papers.nips.cc/paper_files/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html
  Wiggins, A. (2011). The Twelve-Factor App. https://12factor.net/
  Lee, H. (2026). A Taxonomy of RL Environments for LLM Agents. Han, Not Solo. https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/
  Workman, G. (2026). How Ramp built a full-context background coding agent on Modal. Modal Blog. https://modal.com/blog/how-ramp-built-a-full-context-background-coding-agent-on-modal
  Lopopolo, R. (2026). Harness engineering: leveraging Codex in an agent-first world. OpenAI. https://openai.com/index/harness-engineering/
  AWS. (2025). Amazon Bedrock AgentCore. https://aws.amazon.com/bedrock/agentcore/
  Microsoft. (2024). Azure Container Apps Dynamic Sessions. https://learn.microsoft.com/en-us/azure/container-apps/sessions
  E2B. Open-source runtime for AI agents. https://e2b.dev/
  Modal. Sandboxes documentation. https://modal.com/docs/guide/sandbox
  Modal. (2025). Serverless HTTP. https://modal.com/blog/serverless-http
  Hu, et al. (2025). Step-DeepResearch. arXiv. https://arxiv.org/abs/2512.20491
  Firecracker. AWS open-source microVM. https://firecracker-microvm.github.io/
  Cognition. (2026). What We Learned Building Cloud Agents. https://cognition.ai/blog/what-we-learned-building-cloud-agents
  Northflank. (2026). Top Modal Sandboxes Alternatives for Secure AI Code Execution. https://northflank.com/blog/top-modal-sandboxes-alternatives-for-secure-ai-code-execution
  Google. gVisor — application kernel for containers. https://gvisor.dev/
  Kata Containers. Lightweight VMs that feel like containers. https://katacontainers.io/
  Cloudflare. How Workers works — V8 isolates. https://developers.cloudflare.com/workers/reference/how-workers-works/


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {Hidden Technical Debt of AI Systems: Agent Runtime},
    year = {2026},
    month = {04},
    day = {24},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/04/24/hidden-technical-debt-agent-runtime/}
}



        </description>

        <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/04/24/hidden-technical-debt-agent-runtime/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/04/24/hidden-technical-debt-agent-runtime/</guid>

      </item>

    

      <item>

        <title>&quot;Determinism&quot; is the Biggest Cope in AI Adoption</title>

        <description>

          

          We’ve never had determinism in software. We just had the illusion of it.

Here’s a fact that most people outside computer science don’t know: in 1936, Alan Turing proved that there is no way to build a program that can check whether another program will even finish running. This is the Halting Problem. A few years later, Rice’s theorem took this further — Henry Gordon Rice proved that it is mathematically impossible to build a tool that can verify any meaningful property of software in the general case. Not hard. Not expensive. Impossible.


  This means “make sure it doesn’t make a mistake” software was never a guarantee anyone could offer. Every piece of software you trust today shipped with that same uncertainty.


So when someone says “I can’t use LLMs in production because they’re nondeterministic — we need to build deterministic workflows where making no mistakes is the baseline expectation,” they’re confusing repeatability with correctness. A deterministic program that returns the wrong answer returns it every single time. That’s a bug, not a baseline expectation.

Manufacturing figured this out decades ago. Six Sigma doesn’t demand zero defects — it defines an acceptable defect rate and builds measurement systems to stay within that bound. The discipline was never “eliminate all variation.” It was “define, measure, analyze, improve, control” — continuously reducing variation. That’s evaluation, not determinism.

What AI systems shift is the evaluation surface. Instead of “does this code path execute as specified,” you ask “does this output meet our evaluation criteria.” The work moves from pre-deployment code verification to continuous evaluation.


  In AI and machine learning systems, we reduces entropy (chaos) through evaluation.


This is not new. TCP connects on unreliable networks. RAID clusters operate on top of failing drives. AI models are trained on failing GPUs. We’ve always built reliable systems from unreliable components.


  It was never about determinism. It was always about evaluations. If this resonates, I go deep on evaluation design in my book.



        </description>

        <pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/04/07/determinism-biggest-cope-in-ai-adoption/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/04/07/determinism-biggest-cope-in-ai-adoption/</guid>

      </item>

    

      <item>

        <title>The AI Great Leap Forward</title>

        <description>

          Backyard furnaces, fake grain reports, dead sparrows, and poisoned flowers — your company&apos;s AI transformation is repeating history. - 

          In 1958, Mao ordered every village in China to produce steel. Farmers melted down their cooking pots in backyard furnaces and reported spectacular numbers. The steel was useless. The crops rotted. Thirty million people starved.

In 2026, every other company is having top down mandate on AI transformation.

Same energy.



Backyard Furnaces

The rallying cry of the Great Leap Forward was 超英趕美 — surpass England, catch up to America. Every province, every village, every household was expected to close the gap with industrialized Western nations by sheer force of will. Peasants who had never seen a factory were handed quotas for steel production. If enough people smelt enough iron, China becomes an industrial power overnight. Expertise was irrelevant. Conviction was sufficient.

The mandate today is identical, just swap the nouns. Every company, every function, every individual contributor is expected to close the AI gap. Ship AI features. Build agents. Automate workflows. That nobody on the team has ever trained a model, designed an evaluation system, or debugged a retrieval system is beside the point. Conviction is sufficient.

So everyone builds. PMs build AI dashboards. Marketing builds AI content generators. Sales ops builds AI lead scorers. Software engineers are building AI and data solutions that look pixel-perfect and function terribly. The UI is clean. The API is RESTful. The architecture diagram is beautiful. The outputs are wrong. Nobody checks because nobody on the team knows what correct outputs look like. They’ve never looked at the data. They’ve never computed a baseline.


  


Entire departments are stitching together n8n workflows and calling it AI — dozens of automated chains firing prompts into models, zero evaluation on any of them. These tools are merchants of complexity: they sell visual simplicity while generating spaghetti underneath. A drag-and-drop canvas makes it trivially easy to chain ten LLM calls together and impossibly hard to debug why the eighth one hallucinates on Tuesdays. The people building these workflows have never designed an evaluation pipeline, never measured model drift, never A/B tested a prompt. They don’t need to — the canvas looks clean, the arrows point forward, the green checkmarks fire. The complexity isn’t avoided. It’s hidden behind a GUI where nobody with ML expertise will ever look.

The backyard steel of 1958 looked like steel. It was not steel. Today’s backyard AI looks like AI. It is not AI. A TypeScript workflow with hardcoded if-else branches is not an agent. A prompt template behind a REST endpoint is not a model. Calling these things AI is like calling pig iron from a backyard furnace high-grade steel. It satisfies the reporting requirement. It fails every real-world test.

But the most dangerous furnace is the one that produces something functional. Teams are building demoware — pretty interfaces, working endpoints, impressive walkthroughs — with zero validation underneath. Some are in-housing SaaS products by vibe coding some frontend with coding agents: it runs, it has a dashboard, it cost a fraction of the vendor. Klarna announced in 2024 that it would replace Salesforce and other SaaS providers with internal AI-built solutions. What these replacements don’t have is data infrastructure, error handling, monitoring, on-call support, security patching, or anyone who will maintain them after the builder gets promoted and moves on.

These apps will win awards at the next all-hands. In two years they’ll be unmaintainable tech debt some poor soul inherits and rewrites from scratch. The furnace produced pig iron. Someone stamped “steel” on it. Now it’s load-bearing.

Meanwhile, the actual product that customers pay for rots in the field. But hey, 超英趕美. The AI adoption dashboard is green.

Reporting Grain Production to the Central Committee

During the Great Leap Forward, provinces competed to report the most spectacular grain yields. Hubei reported 10,000 jin per mu. Guangdong said 50,000. Some counties claimed over 100,000 — physically impossible numbers, rice plants supposedly so dense that children could stand on top of them. Officials staged photographs. Everyone knew the numbers were fake. Everyone reported them anyway, because the alternative was being labeled a saboteur. The central government, delighted by the bounty, increased grain requisitions based on the reported yields. Farmers starved eating the difference between the real number and the fantasy.

You’ve seen this meeting.

One team reports their AI copilot “reduced development time by 40%.” The next team, not to be outdone, reports 60%. A third claims their AI agent “automated 80% of analyst workflows.” Nobody asks how these were measured. Nobody checks the methodology. Nobody points out that the team claiming 80% automation still has the same headcount doing the same work. The numbers go into a slide deck. The slide deck goes to the board. The board is delighted. The board increases investment.


  


Then someone — there’s always someone — builds a leaderboard tracking how many prompts you wrote this week, how much of your code is AI-generated, your ranking versus your team, versus your org, versus the entire company. One day your company announces: stop everything, it’s AI Week. Build something with AI. Show what you’ve got. You think you’re done after the hackathon? No no no. Now you have to promote it. Daily posts: look what I built, here’s how many agents I used, here’s how many skills I shipped. Pull in teammates. Pull in strangers. Ask for feedback. “Humbly.”

Your AI usage is now a KPI. You are being evaluated on how much grain you reported, not how much grain you grew. This is Goodhart’s Law at organizational scale: when a measure becomes a target, it ceases to be a good measure. The metric was supposed to track whether AI is making the company better. Instead, the entire company is now optimizing to make the metric look better. The beatings will continue until adoption improves.

Killing the Sparrows

The Great Leap Forward’s most tragicomic chapter was the 除四害运动 (Eliminate Four Pests Campaign). Mao declared sparrows an enemy of the state — they ate grain seeds, so killing them would increase harvests. The entire country mobilized. Citizens banged pots and pans to keep sparrows airborne until they dropped dead from exhaustion. Children climbed trees to smash nests. Villages competed for the highest kill count. It worked. They nearly eradicated sparrows.

Then the locusts came.

Sparrows ate locusts. Without sparrows, locust populations exploded. The swarms devoured far more grain than the sparrows ever did. The campaign to save the harvest destroyed it. Mao quietly replaced sparrows with bedbugs on the official pest list and never spoke of it again.

Every AI Great Leap Forward has its sparrow campaign.

Middle managers are the sparrows. They’re declared pests — too many layers, too slow, too expensive. Flatten the org! Move faster! Let AI handle coordination! So companies eliminate M1s, turn managers into tech leads running pods, and let the teams self-organize with AI tools.


  


Then the locusts come. Those middle managers held institutional knowledge — which customer had the weird integration, why the data model had that inexplicable column, the undocumented business rule that kept compliance from flagging every third transaction. That context lived in their heads. Now they’re gone, and the AI system they were replaced with needs exactly that context to function.

QA is a sparrow too. “AI writes the tests now.” So you cut QA. The AI writes tests that validate its own assumptions — a machine checking its own homework. Senior engineers who mentored juniors? Sparrows. Documentation writers? Sparrows. The ops team that knew how to restart the weird legacy service at 2 AM? Definitely sparrows.

Each elimination looks rational in isolation. The second-order effects arrive six months later, and by then nobody connects the locust swarm to the dead sparrows.

Let a Hundred Skills Bloom

In 1956, Mao launched the 百花运动 (Hundred Flowers Campaign): “Let a hundred flowers bloom, let a hundred schools of thought contend.” Speak freely. Share your honest criticisms. The Party wants to hear your real thoughts.

Intellectuals took the bait. They spoke openly.

Then came the 反右运动 (Anti-Rightist Campaign). Everyone who had spoken honestly was identified, labeled, and purged. The Hundred Flowers was a trap — an efficient mechanism for surfacing exactly who knew what, then eliminating them. The lesson every survivor internalized: never honestly reveal what you know, because it will be used against you.

Now Meta and a growing list of companies have launched their own Hundred Flowers. The mandate: every employee must build “agent skills” — distill your subject matter expertise into structured prompts and workflows that AI agents can execute. Or even worse, build “agents” using some drag and drop legacy tech that never worked and had already been given up by the leading edge labs back in 2024. Encode your judgment. Document your decision-making. Make yourself legible to the machine.


  


The stated goal is distilling your subject matter expertise. Turn the expert’s craft into the organization’s asset. What leadership actually wants is to convert individual human capital into organizational capital that survives any single employee’s departure.

Employees see the game immediately. If I distill my ten years of domain expertise into a skill that any junior can invoke with a prompt, I have just automated my own replacement. The knowledge that makes me the critical node — the person they call at 2 AM, the one who knows why the model does that weird thing for Brazilian entities — is my moat. You’re asking me to drain it.

So they adapt to build anti-distillation agent skills, just as the intellectuals adapted after the Anti-Rightist trap.

We are already seeing agent skills built specifically for job security. The performative skill looks comprehensive and demos well but omits the 20% of edge-case knowledge that makes it work in production — you are now more indispensable, not less. The poison pill encodes expertise faithfully but with subtle dependencies on context only you hold — internal wikis you maintain, terminology you coined, data pipelines you own — so removing you causes outputs to drift quietly until someone says “we need to bring them back on this.” The complexity moat makes the skill so architecturally entangled with your other work that extracting your knowledge is harder than keeping you around. You are now a load-bearing wall disguised as a decoration.

The campaign designed to reduce organizational dependence on individual experts has now created experts who are strategically indispensable — not because of what they know, but because of how they’ve booby-trapped the system to need them. The flowers bloomed. They’re full of thorns.

Meanwhile, the “everyone builds with AI” mandate has turned into a hunger game of scope creep. Engineers use AI to generate designs and ship prototypes without waiting for the design team. PMs use AI to write code and spin up dashboards without filing engineering tickets. Designers use AI to build product specs and run user research without looping in product. Everyone is expanding into everyone else’s territory — not because they’re better at it, but because AI makes it possible and the mandate makes it rewarded. The org chart says collaboration; the incentive structure says land grab. What looks like productivity gains is actually a war of all against all, where every function is simultaneously trying to prove it can absorb the others before the others absorb it.


  


The Famine Comes Later

The Great Leap Forward’s famine didn’t arrive immediately. For a while, the numbers looked spectacular. Every province reported record harvests. Leadership was pleased. The requisitions increased.

The famine came when the real grain ran out but the reported grain kept flowing upward.

We’re still in the reporting phase. The dashboards are green. Adoption is up and to the right. Every team reports productivity gains that, if summed across the company, would imply engineers are shipping at 300% efficiency while somehow still missing the same deadlines.

Underneath the metrics, it’s a race to the bottom. One person builds a skill, so someone else builds a better one. One person demos a prototype, so someone else benchmarks it. Everyone competing to prove, more thoroughly than the next person, that their own role is replaceable. All accelerating. All sinking.

The sparrows are dead. The locusts haven’t arrived yet. The flowers bloomed full of poison pills. The furnaces produced pig iron stamped as steel that’s now load-bearing. The grain numbers look fantastic.

But it’s fine. We’re surpassing and catching up.

Oh, and Klarna? The company that loudly announced it would replace Salesforce with internal AI solutions? They quietly replaced Salesforce with another SaaS vendor instead. The backyard furnace couldn’t produce real steel. They bought it from a different mill.

The question nobody’s asking: what did any of this actually produce?

The answer, when it arrives, will be awkward.

References


  Kafka, P. (2026). Meta’s AI week shows how every company is pushing employees to use AI. Business Insider. https://www.businessinsider.com/meta-ai-week-employee-training-claude-agents-vibe-coding-2026-3
  leilei926524-tech. (2026). anti-distill. GitHub. https://github.com/leilei926524-tech/anti-distill
  Blum, S. (2024). Klarna Plans to Shut Down SaaS Providers and Replace Them With AI. Inc. https://www.inc.com/sam-blum/klarna-plans-to-shut-down-saas-providers-and-replace-them-with-ai.html
  CX Today. (2025). Klarna Didn’t Replace Salesforce — It Replaced Them With Alternative SaaS Apps. https://www.cxtoday.com/crm/klarna-didnt-replace-salesforce-it-replaced-them-with-alternative-saas-apps/


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {The AI Great Leap Forward},
    year = {2026},
    month = {04},
    day = {05},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/04/05/the-ai-great-leap-forward/}
}



        </description>

        <pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/04/05/the-ai-great-leap-forward/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/04/05/the-ai-great-leap-forward/</guid>

      </item>

    

      <item>

        <title>A Taxonomy of RL Environments for LLM Agents</title>

        <description>

          The infrastructure that determines what your agent can actually learn - 

          Model architecture gets all the attention. Post-training recipes follow close behind. The reinforcement learning (RL) environment — what the model actually practices on, how its work gets judged, what tools it can use — barely enters the conversation. That’s the part that actually determines what the agent can learn to do.

A model trained only on single-turn Q&amp;amp;A will struggle the moment you ask it to maintain state across a 50-step enterprise workflow. A model trained with a poorly designed reward function will learn to game the metric and not solve the problem. Reinforcement learning environments is half the system.

The Canonical Loop

Recall that reinforcement learning is an interdisciplinary area of machine learning and optimal control concerned with how an intelligent agent should take action in a dynamic environment in order to maximize a reward signal. It involves a set of agent and environment states $S$, a set of actions (action space) available for the agent $A$, and the immediate reward $R_t$ after transition from $S_t$ to $S_{t+1}$ under action $A_t$.



If we take this model into the world of AI agents under the assumption of enabling training of agentic models, we can mutate the framework as follows. An RL environment for an LLM agent bundles the following objects: a dataset of task inputs, a harness for the model, a reward function to score outputs, the state of the environment, and configurations of the environment. Note that we specifically bundle tasks with the environments as tasks are most often environment dependent. As an example, a coding task is bundled with a coding environment, not with a research environment. With this framing, the training loop looks like this:



Formally, a complete RL environment is a set:

\[E = \{T, H, V, S, C\}\]

where

$T$ = tasks

$H$ = agent harness

$V$ = verifier

$S$ = state management

$C$ = configuration

Let’s go through each of the components.

$T$: Tasks

Tasks are a set of problems the agent tries to solve within their environments. Not all tasks are equal, and not just in difficulty. They vary structurally in ways that demand different capabilities. This includes the number of actions an agent need to take to complete the task, the number of distinct tools in the environment an agent need to use, the number of token consumed, the amount of time it takes to complete tasks. These can be captured in various distributions such as:


  
    
      Task Type
      What the Agent Must Do
      Example Systems
    
  
  
    
      Single-turn Q&amp;amp;A
      One prompt → one response, check answer
      Math benchmarks, SimpleQA
    
    
      Multi-hop search
      Chain searches, synthesize sources
      BrowseComp, WebWalkerQA
    
    
      Open-ended research
      No single correct answer; report quality matters
      ADR-Bench, ResearchRubrics
    
    
      Agentic tool-use
      Call tools correctly in sequence
      tau-bench, function-calling benchmarks
    
    
      Stateful enterprise
      Modify persistent DB state, work within access controls
      EnterpriseOps-Gym
    
    
      Code generation
      Write code, run it, check outputs
      SWE-Bench, LiveCodeBench
    
    
      Code review &amp;amp; repair
      Detect bugs, suggest fixes, verify patches
      CodeReview-Bench, DebugBench
    
    
      Repository-level coding
      Navigate large codebases, multi-file edits, resolve issues
      SWE-Bench Verified, RepoBench
    
    
      Productivity workflows
      Draft emails, manage calendars, triage notifications
      WorkArena, OSWorld
    
    
      Document authoring
      Create, edit, or summarize documents across apps
      BrowserGym, GAIA
    
  



  In RL, the sequence of states, actions, and rewards that an agent produces while solving a task is called a trajectory. A single run from start to completion is an episode, and the process of executing a policy to generate a trajectory is called a rollout. In the agent world, a logged record of an agent’s execution — including tool calls, observations, and intermediate outputs — is called a trace. A trajectory is what the trainer sees (state-action-reward tuples); a trace is what the observability system sees (structured execution logs).


Designing the set of task with a proper distribution is an important data design decision. Agentic models need to be able to explore the environment to learn. This means that if agents is trained only in clean and determinsitc environment will most likely not know how to respond in more stochastic production environments. Or an agent will not be able to learn if there’s always a positive reward; it simply have no way to distinguish good actions from bad ones.

The lowest cost to collect tasks are single-turn with verifiable answers. The most valuable tasks for long-horizon behavior are expensive to construct. This tension drives most environment design decisions. In addition, we can construct a curriculum of tasks based on the difficulties. Similar to how human learns math in progressive difficulties, e.g., from 9th grade algebra to 12th grade calculus, we can order tasks by difficulty and increase complexity during training.

Synthetic data for tasks is increasingly a first-class problem. With real-world productivity and research tasks, you rarely have a large labeled dataset. Strategies for generating synthetic tasks include:


  Back translation: Start from a desired output, reconstruct the task input that would produce it
  Graph-based synthesis: Build a knowledge graph, generate multi-hop queries over it


$H$: Agent Harness

The harness is the scaffolding that enables the model to interact with the environment. This controls how the model interacts, but it does not improve what it knows.

We can define harness as follows:

H = {
    rollout_protocol,   # SingleTurn | MultiTurn | Agentic
    tools,              # Available tools in for a roll-out in an environment
    system_prompt,      # Instructions for the agent
    context_manager,    # How to handle context overflow
    turn_limit,         # Max interactions for a roll-out in an environment
    sandbox,            # Code execution sandbox
    state               # Persistent state across turns
}


Rollout protocols range from trivial to complex:


  
    
      Harness Type
      Description
      When to Use
    
  
  
    
      Single-Turn
      One prompt, one response
      Math, factual QA
    
    
      Multi-Turn
      Back-and-forth dialogue
      Games, structured tasks
    
    
      Tool-Use
      Model calls tools, receives results
      Agent benchmarks
    
    
      Stateful Tool-Use
      Tools modify persistent state
      Enterprise workflows, SWE-Bench
    
    
      Agentic
      Full Observation→Orient→Decide→Act (OODA) loop
      Deep research, complex workflows
    
  


Tools span a wide taxonomy:


  
    
      Category
      Tools
      Deterministic?
      Stateful?
    
  
  
    
      Information retrieval
      web_search, scholar_search
      No (live web)
      No
    
    
      Content extraction
      jina_reader, visit, web_scrape
      No
      No
    
    
      Code execution
      python_interpreter, shell, sandbox
      Yes (given same code)
      Yes
    
    
      File operations
      file_read, file_write
      Yes
      Yes
    
    
      Browser automation
      playwright, link_click
      No
      Yes
    
    
      Task management
      todo, section_write
      Yes
      Yes
    
  


The mix of deterministic/non-deterministic and stateful/stateless tools impacts reproducibility and reward assignment. Non-deterministic tools mean two runs of the same trajectory can produce different outcomes — which complicates both debugging and verifier design.

Note that modern designs of agent harnesses reduces the number of tools down to atomic basics, often with read, write, edit, bash, and tasks that kicks off a subprocess for subagents, mcp for connecting to MCP resources, skill and askUserQuestions for managing agent skills and human agent interfaces (HAI). This is distinctively different from early days of LLM based AI agents where we manually adding individual tools such as API calls or database connections.

Context management is critical for long-horizon tasks. The role of the harness here is analogous to an operating system: just as an OS abstracts away memory and process management so applications don’t have to, the agent harness manages context so that agent skills and users don’t need to. A 600-turn research episode blows past any practical context window. Strategies used in production:


  
    
      Strategy
      Description
      Trade-off
    
  
  
    
      Recency-based retention
      Keep N most recent turns
      Simple, but loses early context
    
    
      Markovian reconstruction
      Reconstruct state from scratch each turn
      Principled, expensive
    
    
      Reference-preserving summarization
      Summarize old context, keep citations
      Preserves verifiability
    
    
      Reference-preserving folding
      Compress context without losing references
      Best for research tasks
    
  


An agent doing multi-hour research needs to remember why it started searching in a particular direction twelve tool calls ago. Dropping that context causes repeated work and lost threads.

V: Verifier

The verifier maps a completion to a reward:

\[V: (\text{task prompt}, \text{completion}, \text{info}) \rightarrow [0, 1]\]

In Atari, the score is unambiguous. In coding, verification is straightforward when tests pass, but gets murkier — what about code that is correct but poorly styled or computationally expensive? In deep research, what counts as a good answer is far more ambiguous. This is the generation-verification gap: generating outputs with AI agents is cheap, but verifying their quality becomes progressively harder as tasks grow more open-ended. The goal of the verifier is to map a large, stochastic space of inputs and outcomes into a narrow reward signal, typically between 0 and 1. Designing this mapping is a core challenge in building RL environments.


  
    
      Type
      Reward Signal
      When to Use
    
  
  
    
      Exact match
      Binary (0/1)
      Ground truth available
    
    
      Code execution
      Binary or partial
      Output can be tested programmatically
    
    
      LLM-as-judge
      Continuous [0,1]
      Open-ended quality, no other option
    
    
      Checklist-style
      Continuous
      Multi-criteria research tasks
    
    
      Evolving rubric (RLER)
      Continuous
      Resistant to reward hacking
    
    
      Process reward model (PRM)
      Per-N-step continuous
      Long-horizon credit assignment
    
    
      Pairwise comparison
      Relative rank
      Relative quality matters more than absolute
    
    
      Multi-criteria composite
      Weighted sum
      Multiple quality dimensions
    
  


A few principles that actually matter in practice:

Verifiable beats judgeable. Programmatic checks such as string match or code execution, are faster, cheaper, and more consistent than LLM-as-judge. Use LLM-as-judge when there’s no other option, not as the default.

Reward granularity is a separate decision from reward type. You can score at the trajectory level (did the final output pass?), turn level (was each tool invocation useful?), or per-step with process rewards. Turn-level supervision, as Nanbeige4.1 does across up to 600 tool calls, enables finer credit assignment — the model can learn that the problem was a bad search query in turn 23, not that the entire episode failed. Think of it like project management; we only need to check if the lightbulb is lit if we are changing a lightbulb, but we will need regular inspections and milestones if we are doing a full kitchen remodeling.

Static rubrics get gamed. Models learn to write answers that score well on your rubric rather than solving the problem. DR Tulu’s RLER (Rubric-Level Evolving Reward) co-evolves the rubric with the policy during training. Harder to exploit a moving target.

Noise injection is underrated. Step-DeepResearch (Hu et al., 2025) deliberately injects 5–10% tool errors during training. The resulting model handles flaky APIs and unexpected failures in production significantly better.

$S$: State and $C$: Configuration

Every agent needs an environment to act in, and environments vary widely. A Pokémon Ruby agent plays the game itself, with all its controls and mechanics. A coding agent typically operates inside a virtual machine with code repositories and instructions such as AGENTS.md that guide the agent; it can also execute code in the VM to verify correctness. A deep research agent uses a VM as a scratch pad with access to the internet or knowledge bases to produce a comprehensive research report.

Some environments are stateless — each episode starts fresh with no memory of prior runs. A coding agent solving LeetCode problems needs no persistent state. But some environments are stateful: a coding agent that must manipulate a database carries state across actions, and an enterprise agent carries state across episodes. EnterpriseOps-Gym (Zhang et al., 2026) maintains 164 database tables and 512 tools across episodes, where actions in one task affect the state seen by subsequent tasks. That’s a fundamentally different problem for agents to learn.

Automated environment generation is an emerging approach to scaling environment diversity. Rather than hand-authoring environments, LLM coding agents write new environment code. AutoEnv (Wang et al., 2025) reports ~$4/env average cost.

Configuration covers turn limits, context budgets, sampling temperature, and curriculum scheduling. These are not afterthoughts — a turn limit of 5 vs. 600 changes what skills the agent can develop. AgentScaler (Pan et al., 2025) uses a two-phase curriculum — fundamental capabilities first, then domain-specific tasks — and the ordering matters. Step-DeepResearch progressively scales context windows from 32K to 128K during mid-training.

Deployment topology. In practice, the trainer, model inference server, and environment typically run as separate processes communicating via API — as shown in the canonical loop diagram. This split lets you scale inference and environment execution independently and swap models without rewriting environment code.

Benchmarks: Frozen Environments

If you’ve built benchmarks before, you’ve already built an RL environment — just a frozen one. Press (2026) defines a benchmark as a 4-tuple:

\[B = (\text{Request}, \text{Environment}, \text{Stopping Criteria}, \text{Scorer})\]


  request is the task prompts, which maps to $\textbf{T}$ (tasks) in our RL enviroment.
  environment is the sandbox the model operates in, including tools, APIs, file systems. This is a subset of RL enviroment with only $\textbf{H}$ (harness) and $\textbf{S}$ (state).
  Stopping criteria define when an episode ends — turn limits, timeouts, or the model declaring it’s done. This is the $\textbf{C}$ (configuration) part of the RL environment.
  scorer maps the model’s output to a grade, which is the $\textbf{V}$ (verifier) in RL environment.


The difference is that a benchmark freezes every component to enable reproducibility across runs.

Because benchmarks and training environments share the same components, the design principles that make benchmarks good apply directly to training environments — with one key difference: training environments can evolve their parameters over the course of a run.

Task naturalness. SWE-bench (Jimenez et al., 2024) works because its tasks are real GitHub issues filed by real developers — not synthetic problems invented by researchers. Press (2026) argues that a useful benchmark should contain tasks that actual humans perform frequently and that a system scoring well on them would save someone real time. The same applies to training: an agent trained on tasks no human would actually encounter may ace your eval without learning to be useful. When generating tasks at scale, naturalness separates curriculum from noise.

Automatic, verifiable scoring. If a benchmark requires human judges, it can’t scale. If a training environment requires human judges, it can’t train. The principle is identical but the stakes are higher — training runs may need millions of reward signals, not hundreds. This is why the “verifiable beats judgeable” principle from the verifier section matters even more at training time.

Difficulty calibration. Press recommends launching benchmarks with top-model accuracy between 0.1% and 9%. The training analog: if your task distribution is too easy, the agent ceilings quickly and stops improving. If it’s too hard, the reward signal is too sparse to learn from. The sweet spot shifts as the model improves, which is why training environments — unlike benchmarks — benefit from curriculum scheduling that benchmarks can’t do. That’s the extra degree of freedom.

Scorer independence. Using the same model family to both generate completions and judge them creates a feedback loop — the agent learns to write prose that sounds good to its own judge rather than prose that’s correct. In benchmarks, this inflates scores. In training, it’s worse: it actively teaches the wrong behavior. If you must use LLM-as-judge, the judge should be a different model class than the policy, and ideally one the training signal can’t update.

The difference between a benchmark and a training environment is that benchmarks freeze; training environments evolve. Task distributions shift via curriculum. Verifier rubrics co-evolve with the policy (RLER). Configuration parameters scale up over training. But the underlying components — and the principles that make them good or bad — are the same.

Additional Considerations

Environment diversity matters as much as environment quality. AgentScaler’s key finding is that heterogeneity of environments drives capability breadth in ways that simply adding more data from the same distribution cannot. You need more kinds of environments, not just more environments.

Automated environment generation is viable. At $4 per generated environment, cost is no longer the bottleneck. The bottleneck is verifier quality — auto-generated environments with weak reward functions will teach the wrong behaviors at scale. (AutoEnv)

The environment-as-package model is winning — and becoming a managed service. The Prime Intellect Environments Hub created a shared ecosystem around RL environments, in the same way PyPI and HuggingFace created ecosystems around code and model weights. OpenReward (General Reasoning, 2026) pushes this further by serving 330+ RL environments as managed API endpoints backed by 4.5M+ tasks and autoscaled sandbox compute. The underlying protocol — the Open Reward Standard (ORS) — extends MCP (Anthropic, 2024) with RL primitives: episodes, reward signals, task splits, and curriculum management. ORS is to RL environments what MCP is to tool integration: a shared interface that decouples the environment from the trainer. Environments published once, consumed by any trainer, hosted or self-served.

Contamination resistance will become a design requirement. As RL environments are reused across labs and open-source efforts, data contamination — models memorizing benchmark answers from pre-training — becomes a real threat to training signal validity. Environments that support held-out task splits, dynamic task generation, or verifier-side answer withholding will age better than static datasets. SciCode (Tian et al., 2024) demonstrates this with multi-step scientific problems designed to resist memorization through compositional subproblem structure.

Conclusion

RL environments are the training grounds that shape what agents can do. The task distribution determines what skills the agent develops. The harness controls how it interacts. The verifier defines what “good” means. The state and configuration determine how realistic the training is. Get these right, and the agent learns behaviors that transfer to production. Get them wrong, and you’ve trained an expensive demo.

References


  Sutton, R. S., &amp;amp; Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.). MIT Press.
  Lee, H. (2026). It’s-a Me, Agentic AI. Han, Not Solo. https://leehanchung.github.io/blogs/2026/02/18/mario-agentic-ai/
  Jimenez, C. E., Yang, J., Wettig, A., Yao, S., Pei, K., Press, O., &amp;amp; Narasimhan, K. (2024). SWE-bench: Can Language Models Resolve Real-World GitHub Issues? arXiv. https://arxiv.org/abs/2310.06770
  Tian, M., et al. (2024). SciCode: A Research Coding Benchmark Curated by Scientists. arXiv. https://arxiv.org/abs/2407.13168
  Anthropic. (2024). Model Context Protocol. https://modelcontextprotocol.io/
  Pan, J., et al. (2025). AgentScaler: Scaling LLM Agent Training with Automatically Constructed Environments. arXiv. https://arxiv.org/abs/2509.13311
  Wang, Y., et al. (2025). AutoEnv: Towards Automated Reinforcement Learning Environment Design. arXiv. https://arxiv.org/abs/2511.19304
  PrimeIntellect. (2025). Prime RL Environments Hub. GitHub. https://github.com/PrimeIntellect-ai/prime-rl
  Press, O. (2026). How to Build Good Language Modeling Benchmarks. https://ofir.io/How-to-Build-Good-Language-Modeling-Benchmarks/
  Zhang, K., et al. (2026). EnterpriseOps-Gym: A Benchmark for Enterprise Operations Agents. arXiv. https://arxiv.org/abs/2603.13594
  General Reasoning. (2026). OpenReward: Managed RL Environments API. https://docs.openreward.ai/
  Open Reward Standard. (2026). ORS Protocol Specification. https://openrewardstandard.io/


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {The Training Grounds: A Taxonomy of RL Environments for LLM Agents},
    year = {2026},
    month = {03},
    day = {21},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/}
}



        </description>

        <pubDate>Sat, 21 Mar 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/03/21/rl-environments-for-llm-agents/</guid>

      </item>

    

      <item>

        <title>It&apos;s-a Me, Agentic AI</title>

        <description>

          Understanding agentic model development and agent frameworks through the lens of Super Mario - 

          Agentic AI is a fairly recent development that combines reasoning (OpenAI, 2024) and tool use (Schick et al, 2023) in the same AI model. But an agentic AI system is not just the model, but also the harness, environments, tools, rewards, evaluations and benchmarks, and all of the infrastructure to support it. In this post, let’s use Super Mario, the classic Nintendo video game, to tell this story for understanding how agentic AI models are developed, how agent harnesses work, and how reinforcement learning ties everything together. If you survived World 8-4 as a kid, you already have the intuition for building agentic AI systems.


Small Mario: The Base Model



Small Mario is the base pretrained model. He’s just come out of pretraining on a massive corpus of platform game physics. He can walk, jump, and move left and right. These are his base capabilities, the raw knowledge compressed from the training data.

But Small Mario is fragile. One hit from a Goomba and he’s dead. He can’t break bricks or take damage. He has potential, but he’s not yet useful for anything beyond the most trivial tasks.

This is your base LLM fresh off pretraining. It has absorbed enormous amounts of knowledge, it can do next-token prediction, and it can sort-of follow instructions. But ask it to do anything real, reliably, in production, and it falls apart on the first obstacle. One Goomba and it’s game over.



The Super Mushroom is Agent Harness



Then Mario finds the Super Mushroom. He doubles in size. He can now break bricks. He can take a hit without dying. He goes from fragile to capable.

The Super Mushroom is the model harness. Once eaten, it transforms a base model into something production-ready. This includes:


  System prompts that define personality and constraints
  Safety guardrails so it can take some damage without dying
  Memory and context management so it remembers where it’s been
  Tool-use training so it knows power-ups exist and how to grab them


Without the Super Mushroom, Mario is a liability. With it, he now has potential for greatness. Similarly, without the model harness, a base LLM is a research artifact. With it, it’s has the potential to become a product.


  The Super Mushroom doesn’t change WHO Mario is. It changes what he can SURVIVE. The model harness doesn’t change the model’s core knowledge. It changes what the model can handle in production.




Power-Ups are Agent Skills



Now here’s where it gets interesting. Super Mario can pick up power-ups that give him entirely new capabilities. These are agent skills:


  
    
      Power-Up
      Mario Ability
      Agent Equivalent
    
  
  
    
      Fire Flower
      Throw fireballs at enemies
      Code execution — solve problems the model can’t solve with text alone
    
    
      Frog Suit
      Swim through water levels
      Web search — navigate environments the model wasn’t trained on
    
    
      Star
      Temporary invincibility
      Extended thinking — brute force through complex problems at higher compute cost
    
    
      Cape Feather
      Sustained flight
      MCP servers — extensible access to external services and APIs
    
  


Each power-up doesn’t replace Mario’s core abilities. Mario still walks and jumps. The power-ups extend what he can do. A Fire Flower Mario can still jump on Goombas, but now he can also shoot fireballs at Piranha Plants hiding in pipes.

This is exactly how agent skills and tools work. The LLM still does what LLMs do: reasoning, language understanding, and planning. Tools extend the model’s reach into environments it can’t operate in alone. An LLM can’t execute Python by itself, just like Mario can’t throw fireballs without a Fire Flower. But give it the right tool, and suddenly the problem space opens up.

And critically, Mario has to learn WHEN to use each power-up. Frog Suit is amazing in water levels, useless on land. Fire Flower is great against Goombas, pointless against Thwomps. The model needs to learn tool selection, knowing which tool to reach for in which context. This is one of the hardest parts of building agentic systems.



One power-up deserves special attention: the Star. When Mario grabs a Star, he becomes invincible. He plows through Goombas, Koopa Troopas, Piranha Plants, everything in his path just disintegrates. Nothing can stop him.

This is like having an engineering manager who’s really good at clearing organizational blockers for their engineers. The Goombas and Piranha Plants of bureaucracy, cross-team dependencies, access requests, and priority conflicts just melt away. Star power is temporary and expensive, but when you need to blast through a critical path, nothing else comes close.



The Mushroom Kingdom are environments


  


Now let’s talk about the world Mario operates in. Every level in the Mushroom Kingdom is an environment, and every environment is composed of the same building blocks. Some of these building blocks are tools that Mario can use:


  
    
      Level Element
      Environment Equivalent
    
  
  
    
      ? Blocks
      Unknown information sources — sometimes containing exactly what you need
    
    
      Pipes
      Entry points to sub-tasks, function calls, or deeper exploration
    
    
      Goombas
      Common obstacles, predictable errors, edge cases
    
    
      Pits
      Catastrophic failures, unrecoverable errors
    
  


Every level remixes these elements differently. World 1-1 is simple, a few Goombas, some bricks, a clear path to the flag. World 8-4 is a maze of pipes, hidden paths, and a boss fight with Bowser. Same building blocks, radically different difficulty.

Throughout each level, Mario interacts with the environment and the tools contained within. He enters pipes to warp from one place to another, the equivalent of an API call that transports you to an entirely different context. He bumps ? Blocks from below to discover power-ups, new agent skills materializing from the environment when you know where to look. He breaks bricks to clear paths or reveal hidden rewards, structured data yielding its value when you apply force in the right direction. He stomps on a Koopa shell and kicks it forward, turning an obstacle into a projectile that clears a line of Goombas, repurposing error outputs as inputs to solve downstream problems. The environment more than just a backdrop. It’s also a toolbox.


  


But the Mushroom Kingdom isn’t one level. It’s organized into Worlds, each with a distinct theme and set of challenges. World 1 is grassland with basic enemies. World 3 is water. World 6 is ice. World 8 is Bowser’s Castle. Each world is a collection of levels that share a common environment type and difficulty profile.

This maps directly to how we build agentic AI systems for the real world. A single environment — say, a coding sandbox — is one world. But to build an agent that operates across a full domain, you need a collection of world on a World Map: a collection of environments that together cover the breadth of that domain. A coding world includes environments for code generation, code review, rood cause analysis, and operations. An office productivity world includes email, calendar, document editor, and spreadsheets. A research world includes literature search, data analysis, and report writing.


  
    
      World
      Theme
      Agent Domain
    
  
  
    
      World 1
      Grassland
      Simple text tasks, Q&amp;amp;A, summarization
    
    
      World 3
      Water
      Web browsing and API navigation
    
    
      World 6
      Ice
      Debugging in fragile or legacy environments
    
    
      World 8
      Bowser’s Castle
      Full autonomous task completion under adversarial conditions
    
  


Tasks and rewards

Having an environment is not enough. We need to define what we want to achieve from playing the game, and how we measure whether we achieved it. In RL terms, these are the tasks and the reward function.

Mario can play the same level with completely different objectives: complete the level, complete it as fast as possible, get the highest score, collect the most coins, accumulate the most 1-up lives, find all the hidden rewards, stomp on every last Goomba. Each objective produces a fundamentally different play style from the same environment. This is exactly the task definition problem in agentic AI. “Summarize this codebase” and “refactor this codebase” use the same files, the same tools, the same context, but they require entirely different strategies. The task is what transforms an environment from a sandbox into a mission.


  


At the end of every level, there’s a flagpole. Mario jumps on it, pulls down the flag, and receives a reward. The higher he grabs the flag, the bigger the reward. Some levels end with a boss fight against Bowser, where the reward is freeing a Toad (or eventually, Princess Peach). This is the reward signal — the feedback that tells the agent how well it performed the task.

But how do we actually measure how well the game was played? This is reward modeling, and it is where the machine learning engineering discipline really shines. The evaluation could be the raw score, the number of 1-ups gained, coins collected, Goombas stomped, time remaining, or different paths discovered. Most frequently, it is a combination of some or all of the above, weighted and balanced against each other. Do we reward Mario more for speed or for thoroughness? For survival or for aggression? For finding secrets or for staying on the critical path?


  
    
      Evaluation Metric
      Mario Measure
      Agent Measure
    
  
  
    
      Speed
      Time remaining on the clock
      Task completion latency
    
    
      Score
      Points accumulated
      Overall output quality
    
    
      Collection
      Coins gathered
      Information retrieved, resources used efficiently
    
    
      Completeness
      Hidden blocks found, secrets discovered
      Edge cases handled, comprehensive coverage
    
    
      Efficiency
      Enemies defeated per life
      Correct tool invocations per task
    
    
      Exploration
      Different paths taken
      Novel approaches discovered
    
  


Designing these rewards is a rigorous machine learning engineering discipline. A poorly shaped reward function produces an agent that technically completes tasks but in degenerate ways, like a Mario speedrunner who clips through walls. Impressive, but not what we actually wanted. Reward hacking is the Goodhart’s Law of agentic AI: when a measure becomes a target, it ceases to be a good measure.

Reinforcement Learning to learn play

Here’s where the full picture comes together. Reinforcement learning is Mario learn to complete the levels.

Mario starts each level knowing nothing about its specific layout. He has to:


  Observe the current state, what’s on screen, where the enemies are, what power-ups are available
  Decide on an action based on his policy, jump, run, shoot, or wait
  Act and receive feedback from the environment
  Update his policy based on the outcome


This is the MDP (Markov Decision Process) loop. The same loop described in Agents Are Workflows. The same loop that every agentic AI system runs:

\[v^\pi(s) = \mathop{\mathbb{E}}[r(s, a) + \gamma v_\pi(s^\prime)]\]

The value of Mario’s current state equals the expected immediate reward plus the discounted value of the next state. Should Mario jump NOW to get the coin, or wait and avoid the Goomba? The optimal policy $\pi^*$ balances immediate rewards against future outcomes.

Through repeated play (training episodes), Mario learns:

  Which obstacles can be jumped on vs. avoided
  When to use power-ups vs. save them
  Which pipes lead to shortcuts vs. dead ends
  How to handle boss fights


An agentic AI model goes through the same process. Through reinforcement learning (PPO, DPO, GRPO, or whatever the latest acronym is), the model learns:

  Which tools to invoke for which subtasks
  When to think longer vs. act immediately
  Which approaches work for which problem types
  How to decompose complex tasks into manageable steps


And remember: Reinforcement learning does not make the agent harness smarter, nor the power ups. It improves the model and the model only. Thus, the model is the product.

The Engineers behind the controller

So who’s actually making all of this work? Mario doesn’t train himself.

To teach agent Mario to be really good at the game, we employ a Machine Learning Engineer (MLE) — also called a research engineer or applied AI engineer at some organizations. The MLE is the game designer and coach rolled into one. They build the environments that Mario will train in: deciding which levels to include, what obstacles to place, what tools to make available, and how to sequence difficulty so Mario faces progressively harder challenges. They set up the harnesses and tools, define the tasks that Mario needs to achieve, and most importantly, design the reward function. The MLE decides what “good” looks like. Do we reward Mario for speed? Thoroughness? Both? How much? Environment design and reward design are the two highest leverage decisions in the entire pipeline. Get them right and Mario learns to play beautifully. Get them wrong and Mario learns to exploit glitches, or never encounters the challenges he needs to grow.

This isn’t hypothetical. Here’s a real job posting from Anthropic’s Universes team, whose entire job is building training environments for AI models:


  


“Environments where models learn to navigate ambiguity, handle interruptions, maintain context over extended interactions, and exercise judgment in open-ended scenarios.” That’s World 8 — and somebody has to build it.

Once the MLE has designed the training setup, the Machine Learning Systems Engineers (MLSys) take over. They are the ones who actually run the show at scale. They set up the environment and agent Mario across hundreds to hundreds of thousands of environments, tasks and iterations. They manage the compute, the distributed training runs, the data pipelines. They collect the reasoning traces, the sequences of observations, actions, and outcomes from every single episode Mario plays. And from these traces, they run the reinforcement learning algorithms that allow agent Mario to learn from experience.

This is the unsexy but critical part. An MLE can design the most elegant reward function in the world, but without MLSys engineers standing up the infrastructure to run millions of training episodes and collect the resulting data, Mario never gets past World 1-1.

Conclusion

Small Mario needs a mushroom to survive, power-ups to be effective, levels to practice on, and a reward at the flagpole to learn from. That’s the whole agentic AI stack: base model, harness, tools, environments, tasks, rewards, and reinforcement learning.

Now go save Princess Peach.

References

  Sutton, R. S., &amp;amp; Barto, A. G. (2018). Reinforcement Learning: An Introduction (2nd ed.). MIT Press.
  Schick, T., Dwivedi-Yu, J., Dessì, R., Raileanu, R., Lomeli, M., Hambro, E., Zettlemoyer, L., Cancedda, N., &amp;amp; Scialom, T. (2023). Toolformer: Language Models Can Teach Themselves to Use Tools. arXiv preprint arXiv:2302.04761. https://arxiv.org/abs/2302.04761
  OpenAI. (2024). OpenAI o1 System Card. https://cdn.openai.com/o1-system-card-20241205.pdf
  Lee, H. (2025). Agents Are Workflows. Han, Not Solo. https://leehanchung.github.io/blogs/2025/05/09/agent-is-workflow/
  Lee, H. (2025). No Code, Low Code, Real Code. Han, Not Solo. https://leehanchung.github.io/blogs/2025/06/26/no-code-low-code-full-code/


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {It&apos;s-a Me, Agentic AI},
    year = {2026},
    month = {02},
    day = {18},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2026/02/18/mario-agentic-ai/}
}



        </description>

        <pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2026/02/18/mario-agentic-ai/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2026/02/18/mario-agentic-ai/</guid>

      </item>

    

      <item>

        <title>Databricks&apos; Strategic Playbook: Reynold Xin on Growth, AI, and the Future of Data Infrastructure</title>

        <description>

          Apache Spark&apos;s #1 committer reveals how contrarian decisions and AI-first strategy drive Databricks&apos; 60% YoY growth - 

          Reynold Xin, Apache Spark’s #1 committer famous for “deleting more code than others wrote,” reveals how Databricks maintains 60% YoY growth while competitors struggle. In a candid interview at Hysta Rising, he shares the contrarian strategies, technical decisions, and AI-first approach shaping the future of data infrastructure.



The Growth Story: Databricks vs. Snowflake

Databricks has maintained impressive growth metrics, growing 60% year-over-year recently and over 50% YoY currently. Internal growth rates are even higher, though undisclosed. This stands in stark contrast to Snowflake’s current 20-30% YoY growth at similar revenue levels.

Xin provided crucial context: just 2-3 years ago, Snowflake was growing 100% YoY and was considered the fastest-growing public company in history for enterprise go-to-market. However, their decline illustrates a critical strategic lesson.

The GTM Investment Trap

When Wall Street shifted focus from growth to profitability post-ZIRP era, many companies responded by pausing go-to-market (GTM) hiring. This creates a dangerous illusion: immediate profitability improvement masks a growth time bomb.

Why? Account executives and solution architects typically take 1-2 years to become productive. “Pausing does not have any impact on growth for the next year or two. So momentum will continue for a year and then collapse,” Xin explained.

Databricks took the contrarian approach—doubling down on GTM investments while competitors pulled back. This strategic patience is now paying dividends as competitors’ growth rates plummet.

The AI Acceleration

Most enterprises remain primitive in AI/ML/data science adoption, which traditionally generated much smaller revenue than data warehousing. However, 2023 marked a turning point, with growth rates accelerating partly due to generative AI adoption. Databricks now generates over $1 billion ARR from AI products alone.

M&amp;amp;A Strategy: Acquiring DNA, Not Revenue

Databricks’ acquisition strategy differs fundamentally from traditional enterprise approaches:


  Focus on DNA over revenue: “The thesis is never about getting revenue, but getting DNA. Revenue is validation.”
  Target founders with startup DNA: Seek founders who’ve gone through the “5-10 year grind” with hands-on customer experience
  Empower acquired teams: Give them resources to drive new product growth
  Contrast with traditional M&amp;amp;A: Unlike Salesforce or Cisco, which primarily acquire for revenue


The OpenAI Partnership

OpenAI is a significant Databricks customer, and the partnership includes:

  Access to specific models with guaranteed capacity
  $100M capacity deal for on-demand usage
  Strategic decision to focus on high-margin software rather than competing in model training
  Recognition that model serving has “horrible margins” compared to software’s 80-90% margins


Pivotal Moments in Databricks’ Evolution

2015: The PLG Pivot
Started and ended the year with $1M ARR after attempting product-led growth (PLG). The key learning: GTM motion must match the product. Databricks requires VPC peering and production database connections—sensitive operations. This means potential customers can’t simply swipe on a credit card to obtain the service.

2017: Microsoft Azure Partnership
This partnership became a growth catalyst, with Microsoft and Databricks both selling Azure Databricks. At one point, half of growth came from this channel, allowing more efficient sales team scaling.

2020: Multi-Product Expansion
Transitioning from single to multiple products marked a fundamental shift. As Xin noted, “Most companies in Silicon Valley never accomplished second product success.” This multi-year journey included rapid adaptations for generative AI.

Leadership Evolution: From Coder to Executive

Xin’s personal journey reflects a common founder transition:

  First 7 years: “Writing lots of code and building”
  Became a manager reluctantly when “no one wanted to manage that company”
  Built the data warehousing business and took over engineering
  Transitioned from a “hands-on IC to a useless manager over the past 5 years”


Key leadership lessons:

  Delegation mistakes: “Delegated too much was one major mistake”
  Imposter syndrome: Initially deferring too much to hired executives
  Context matters: Realizing that external hires often lack crucial context
  Founder therapy groups: The value of peer support when hiring executives


The Future: AI-Native Databases

Xin sees a massive disruption coming to the $100B OLTP market still dominated by Oracle. The key insight: AI won’t just optimize existing databases—it will fundamentally reimagine how we build and operate data systems.

“Future databases will be provisioned and maintained primarily by AI,” Xin predicts. This isn’t incremental improvement but architectural revolution:

  Self-optimizing schemas: AI dynamically adjusting data models based on query patterns
  Autonomous provisioning: Infrastructure that scales predictively, not reactively
  Intelligent indexing: AI determining optimal indexes in real-time
  Cost collapse: Building and maintaining custom applications becomes 10-100x cheaper


His provocative prediction challenges the entire enterprise software model: “Now there’s no reason for people to buy Workday when you can build bespoke solutions based on company workloads.” When AI can generate and maintain custom applications at marginal cost, why pay for generic SaaS?

Industry Consolidation

The data infrastructure world is consolidating to five major players:

  Three cloud service providers (each with their own offerings)
  Databricks
  Snowflake


“None of them will go away. Smaller players will become irrelevant,” Xin predicts, pointing to the Fivetran-dbt merger as evidence of this trend.



Key Takeaways for AI Engineers

The Databricks story offers crucial lessons for technical leaders navigating the AI transformation:


  
    Margin discipline matters: Xin’s rejection of low-margin model serving in favor of 80-90% margin software shows the importance of business model clarity, even in AI hype cycles.
  
  
    Context beats credentials: Founders who’ve “done the grind” often outperform prestigious hires lacking domain context — a lesson for both hiring and career planning.
  
  
    Timing contrarian bets: While competitors optimize for quarterly earnings, Databricks’ multi-year GTM investment demonstrates how patient capital wins in enterprise markets.
  
  
    AI changes everything: The shift from human-managed to AI-managed infrastructure is a complete reimagining of the $100B+ database market.
  


As Xin’s journey from “writing lots of code” to “useless manager” shows, the path to transforming industries requires both technical depth and strategic courage. In the AI era, those who understand both code and markets will shape the future of enterprise software.



@article{
    leehanchung_databricks_reynold_xin,
    author = {Lee, Hanchung},
    title = {Databricks&apos; Strategic Playbook: Reynold Xin on Growth, AI, and the Future of Data Infrastructure},
    year = {2025},
    month = {11},
    day = {06},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/11/06/raynold-xin-databricks/}
}



        </description>

        <pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/11/06/raynold-xin-databricks/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/11/06/raynold-xin-databricks/</guid>

      </item>

    

      <item>

        <title>Claude Agent Skills: A First Principles Deep Dive</title>

        <description>

          Deconstructing prompt-based meta-tool architecture and context injection patterns for AI engineering - 

          Claude’s Agent Skills system represents a sophisticated prompt-based meta-tool architecture that extends LLM capabilities through specialized instruction injection. Unlike traditional function calling or code execution, skills operate through prompt expansion and context modification to modify how Claude processes subsequent requests without writing executable code.

This deep dive deconstructs Claude’s Agent Skills system from first principles, documents the architecture where a tool named “Skill” acts as a meta-tool for injecting domain-specific prompts into the conversation context. We’ll walk through the complete lifecycle using the skill-creator and internal-comms skill as case studies, examining everything from file parsing to API request structure to Claude’s decision-making process.

Claude Agent Skills Overview

Claude uses Skills to improve how it performs specific tasks. Skills are defined as folders that include instructions, scripts, and resources that Claude can load when needed. Claude uses a declarative, prompt-based system for skill discovery and invocation. The AI model (Claude) makes the decision to invoke skills based on textual descriptions presented in its system prompt. There is no algorithmic skill selection or AI-powered intent detection at the code level. The decision-making happens entirely within Claude’s reasoning process based on the skill descriptions provided.

Skills are not executable code. They do NOT run Python or JavaScript, and there’s no HTTP server or function calling happening behind the scenes. They are also not hardcoded into Claude’s system prompt. Skills live in a separate part of the API request structure.

So what are they? Skills are specialized prompt templates that inject domain-specific instructions into the conversation context. When a skill is invoked, it modifies both the conversation context (by injecting instruction prompts) and the execution context (by changing tool permissions and potentially switching the model). Instead of executing actions directly, skills expand into detailed prompts that prepare Claude to solve a specific type of problem. Each skill appears as a dynamic addition to the tool schema that Claude sees.

When users send a request, Claude receives three things: user message, the available tools (Read, Write, Bash, etc.), and the Skill tool. The Skill tool’s description contains a formatted list of every available skill with their name, description, and other fields combined. Claude reads this list and uses its native language understanding to match your intent against the skill descriptions. If you say “help me create a skill for logs,” Claude sees the internal-comms skill’s description (“When user wants to write internal communications using format that his company likes to use”), recognizes the match, and invokes the Skill tool with command: &quot;internal-comms&quot;.


  Terminology Note:
  
    Skill tool (capital S) = The meta-tool that manages all skills. It appears in Claude’s tools array alongside Read, Write, Bash, etc.
    skills (lowercase s) = Individual skills like pdf, skill-creator, internal-comms. These are the specialized instruction templates that the Skill tool loads.
  


Here’s a more visual representation on skills are used by Claude.



The skill selection mechanism has no algorithmic routing or intent classification at the code level. Claude Code doesn’t use embeddings, classifiers, or pattern matching to decide which skill to invoke. Instead, the system formats all available skills into a text description embedded in the Skill tool’s prompt, and lets Claude’s language model make the decision. This is pure LLM reasoning. No regex, no keyword matching, no ML-based intent detection. The decision happens inside Claude’s forward pass through the transformer, not in the application code.

When Claude invokes a skill, the system follows a simple workflow: it loads a markdown file (SKILL.md), expands it into detailed instructions, injects those instructions as new user messages into the conversation context, modifies the execution context (allowed tools, model selection), and continues the conversation with this enriched environment. This is fundamentally different from traditional tools, which execute and return results. Skills prepare Claude to solve a problem, rather than solving it directly.

The following is a table to help better disambiguating the difference between Tools and Skills and their capabilities:


  
    
      Aspect
      Traditional Tools
      Skills
    
  
  
    
      Execution Model
      Synchronous, direct
      Prompt expansion
    
    
      Purpose
      Perform specific operations
      Guide complex workflows
    
    
      Return Value
      Immediate results
      Conversation context + execution context changes
    
    
      Example
      Read, Write, Bash
      internal-comms, skill-creator
    
    
      Concurrency
      Generally safe
      Not concurrency-safe
    
    
      Type
      Various
      Always &quot;prompt&quot;
    
  


Building Agent Skills

Now let’s examine the skill-creator skill from Anthropic’s repository. Agent skills are organized folders of instructions, scripts, and resources that agents can discover and load for specific tasks. They package expertise into reusable resources that adapt a general-purpose agent to a particular workflow.


  Key Insight: Skill = Prompt Template + Conversation Context Injection + Execution Context Modification + Optional data files and Python Scripts


Every Skill is defined in a markdown file named SKILL.md (case-insensitive) with optional bundled files that’s stored under /scripts, /references, and /assets. These bunlded files can be Python scripts, shell scripts, font definitions, templates, etc. Using skill-creator as an example, it contains SILL.md, LICENSE.txt for the license, and a few Python scripts under teh /scripts folder. skill-creator does not have any /references or /assets.



Skills are discovered and loaded from multiple sources. Claude Code scans user settings (~/.config/claude/skills/), project settings (.claude/skills/), plugin-provided skills, and built-in skills to build the available skills list. For Claude Desktop, we can upload a custom skill as follows.




  NOTE: The most important concept for building Skills is Progressive Disclosure - showing just enough information to help agents decide what to do next, then reveal more details as they need them. In the case of agent skills, it
  
    Disclose Frontmatter: minimal (name, description, license)
    If a skill is chosen, load SKILL.md: comprehensive but focused
    And then load helper assets, references, and scripts as the skill is being executed
  


Writing SKILL.md

SKILL.md is the core of an skill’s prompt. It is a markdown file that follows a two-part structure - frontmatter and content. The frontmatter configures HOW the skill runs (permissions, model, metadata), while the markdown content tells Claude WHAT to do. Frontmatter is the header of the markdown file written in YAML.

┌─────────────────────────────────────┐
│ 1. YAML Frontmatter (Metadata)      │ ← Configuration
│    ---                              │
│    name: skill-name                 │
│    description: Brief overview      │
│    allowed-tools: &quot;Bash, Read&quot;      │
│    version: 1.0.0                   │
│    ---                              │
├─────────────────────────────────────┤
│ 2. Markdown Content (Instructions)  │ ← Prompt for Claude
│                                     │
│    Purpose explanation              │
│    Detailed instructions            │
│    Examples and guidelines          │
│    Step-by-step procedures          │
└─────────────────────────────────────┘


Frontmatter

The frontmatter contains metadata that controls how Claude discovers and uses the skill. As an example, here’s the frontmatter from skill-creator:

---
name: skill-creator
description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude&apos;s capabilities with specialized knowledge, workflows, or tool integrations.
license: Complete terms in LICENSE.txt
---

Lets walk through the fields for the frontmatter one by one.



name (Required)

Self explanatory. Name of the skill. The name of a skill is used as a command in Skill Tool.


  The name of a skill is used as a command in Skill Tool.


description (Required)

The description field provides a brief summary of what the skill does. This is the primary signal Claude uses to determine when to invoke a skill. In the example above, the description explicitly states “This skill should be used when users want to create a new skill” — this type of clear, action-oriented language helps Claude match user intent to skill capabilities.

The system automatically appends source information to the description (e.g., &quot;(plugin:skills)&quot;), which helps distinguish between skills from different sources when multiple skills are loaded.

when_to_use (Undocumented—Likely Deprecated or Future Feature)


  ⚠️ Important Note: The when_to_use field appears extensively in the codebase but is not documented in any official Anthropic documentation. This field may be:
  
    A deprecated feature being phased out
    An internal/experimental feature not yet officially supported
    A planned feature that hasn’t been released
  

  Recommendation: Rely on a detailed description field instead. Avoid using when_to_use in production skills until it appears in official documentation.


Despite being undocumented, here’s how when_to_use currently works in the codebase:

function formatSkill(skill) {
  let description = skill.whenToUse
    ? `${skill.description} - ${skill.whenToUse}`
    : skill.description;

  return `&quot;${skill.name}&quot;: ${description}`;
}


When present, when_to_use gets appended to the description with a hyphen separator. For example:
&quot;skill-creator&quot;: Create well-structured, reusable skills... - When user wants to build a custom skill package with scripts, references, or assets


This combined string is what Claude sees in the Skill tool’s prompt. However, since this behavior is undocumented, it could change or be removed in future releases. The safer approach is to include usage guidance directly in the description field, as shown in the skill-creator example above.

license (Optional)

Self explanatory.

allowed-tools (Optional)

The allowed-tools field defines which tools the skill can use without user approval, similar to Claude’s allowed-tools.

This is a comma-separated string that gets parsed into an array of allowed tool names. You can use wildcards to scope permissions, e.g., Bash(git:*) allows only git subcommands, while Bash(npm:*) permits all npm operations. The skill-creator skill uses &quot;Read,Write,Bash,Glob,Grep,Edit&quot; to give it broad file and search capabilities. A common mistake is listing every available tool, which creates a security risk and defeats the security model.


  Only include what your skill actually needs—if you’re just reading and writing files, &quot;Read,Write&quot; is sufficient.


# ✅ skill-creator allows multiple tools
allowed-tools: &quot;Read,Write,Bash,Glob,Grep,Edit&quot;

# ✅ Specific git commands only
allowed-tools: &quot;Bash(git status:*),Bash(git diff:*),Bash(git log:*),Read,Grep&quot;

# ✅ File operations only
allowed-tools: &quot;Read,Write,Edit,Glob,Grep&quot;

# ❌ Unnecessary surface area
allowed-tools: &quot;Bash,Read,Write,Edit,Glob,Grep,WebSearch,Task,Agent&quot;

# ❌ Unnecessary surface area with all npm commands
allowed-tools: &quot;Bash(npm:*),Read,Write&quot;


model (Optional)

The model field defines which model the skill can use. It defaults to inheriting the current model in the user session. For complex tasks like code review, skills can request more capable models such as Claude Opus or other OSS Chinese models. IYKYK.

model: &quot;claude-opus-4-20250514&quot;  # Use specific model
model: &quot;inherit&quot;                 # Use session&apos;s current model (default)


version, disable-model-invocation, and mode (Optional)

Skills support three optional frontmatter fields for versioning and invocation control. The version field (e.g., version: “1.0.0”) is a metadata field for tracking skill versions, parsed from the frontmatter but primarily used for documentation and skill management purposes.

The disable-model-invocation field (boolean) prevents Claude from automatically invoking the skill via the Skill tool. When set to true, the skill is excluded from the  list shown to Claude and can only be invoked manually by users via `/skill-name`, making it ideal for dangerous operations, configuration commands, or interactive workflows that require explicit user control.

The mode field (boolean) categorizes a skill as a “mode command” that modifies Claude’s behavior or context. When set to true, the skill appears in a special “Mode Commands” section at the top of the skills list (separate from regular utility skills), making it prominent for skills like debug-mode, expert-mode, or review-mode that establish specific operational contexts or workflows.

SKILL.md Prompt Content

After the frontmatter comes the markdown content - the actual prompt that Claude receives when the skill is invoked. This is where you define the skill’s behavior, instructions, and workflows. The key to writing effective skill prompts is keeping them focused and using progressive disclosure: provide core instructions in SKILL.md, and reference external files for detailed content.

Here’s a recommended content structure

---
# Frontmatter here
---

# [Brief Purpose Statement - 1-2 sentences]

## Overview
[What this skill does, when to use it, what it provides]

## Prerequisites
[Required tools, files, or context]

## Instructions

### Step 1: [First Action]
[Imperative instructions]
[Examples if needed]

### Step 2: [Next Action]
[Imperative instructions]

### Step 3: [Final Action]
[Imperative instructions]

## Output Format
[How to structure results]

## Error Handling
[What to do when things fail]

## Examples
[Concrete usage examples]

## Resources
[Reference scripts/, references/, assets/ if bundled]


As an example, skill-creator skill contains the following instructions that specifies each steps of the workflow required to create skills.

## Skill Creation Process

### Step 1: Understanding the Skill with Concrete Examples
### Step 2: Planning the Reusable Skill Contents
### Step 3: Initializing the Skill
### Step 4: Edit the Skill
### Step 5: Packaging a Skill


When Claude invokes this skill, it receives the entire prompt as new instructions with the base directory path prepended. The {baseDir} variable resolves to the skill’s installation directory, allowing Claude to load reference files using the Read tool: Read({baseDir}/scripts/init_skill.py). This pattern keeps the main prompt concise while making detailed documentation available on demand.

Best practices for prompt content:

  Keep under 5,000 words (~800 lines) to avoid overwhelming context
  Use imperative language (“Analyze code for…”) not second person (“You should analyze…”)
  Reference external files for detailed content rather than embedding everything
  Use {baseDir} for paths, never hardcode absolute paths like /home/user/project/


❌ Read /home/user/project/config.json
✅ Read {baseDir}/config.json


When the skill is invoked, Claude receives access only to the tools specified in allowed-tools, and the model may be overridden if specified in the frontmatter. The skill’s base directory path is automatically provided, making bundled resources accessible.

Bundling Resources with Your Skill

Skills become powerful when you bundle supporting resources alongside SKILL.md. The standard structure uses three directories, each serving a specific purpose:

my-skill/
├── SKILL.md              # Core prompt and instructions
├── scripts/              # Executable Python/Bash scripts
├── references/           # Documentation loaded into context
└── assets/               # Templates and binary files


Why bundle resources? Keeping SKILL.md concise (under 5,000 words) prevents overwhelming Claude’s context window. Bundled resources let you provide detailed documentation, automation scripts, and templates without bloating the main prompt. Claude loads them only when needed using progressive disclosure.

The scripts/ Directory

The scripts/ directory contains executable code that Claude runs via the Bash tool—automation scripts, data processors, validators, or code generators that perform deterministic operations.

As an example, skill-creator’s SKILL.md reference scripts like this:
When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.

Usage:

```scripts/init_skill.py &amp;lt;skill-name&amp;gt; --path &amp;lt;output-directory&amp;gt;```

The script:
  - Creates the skill directory at the specified path
  - Generates a SKILL.md template with proper frontmatter and TODO placeholders
  - Creates example resource directories: scripts/, references/, and assets/
  - Adds example files in each directory that can be customized or deleted


When Claude sees this instruction, it executes python {baseDir}/scripts/init_skill.py. The {baseDir} variable automatically resolves to the skill’s installation path, making the skill portable across different environments.

Use scripts/ for complex multi-step operations, data transformations, API interactions, or any task requiring precise logic better expressed in code than natural language.

The references/ Directory

The references/ directory stores documentation that Claude reads into its context when referenced. This is text content—markdown files, JSON schemas, configuration templates, or any documentation Claude needs to complete the task.

As an example, mcp-creator’s SKILL.md reference references like this:
##### 1.4 Study Framework Documentation

**Load and read the following reference files:**

- **MCP Best Practices**: [📋 View Best Practices](./reference/mcp_best_practices.md) - Core guidelines for all MCP servers

**For Python implementations, also load:**
- **Python SDK Documentation**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- [🐍 Python Implementation Guide](./reference/python_mcp_server.md) - Python-specific best practices and examples

**For Node/TypeScript implementations, also load:**
- **TypeScript SDK Documentation**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
- [⚡ TypeScript Implementation Guide](./reference/node_mcp_server.md) - Node/TypeScript-specific best practices and examples


When Claude encounters these instructions, it uses the Read tool: Read({baseDir}/references/mcp_best_practices.md). The content gets loaded into Claude’s context, providing detailed information without cluttering SKILL.md.

Use references/ for detailed documentation, large pattern libraries, checklists, API schemas, or any text content that’s too verbose for SKILL.md but necessary for the task.

The assets/ Directory

The assets/ directory contains templates and binary files that Claude references by path but doesn’t load into context. Think of this as the skill’s static resources - HTML templates, CSS files, images, configuration boilerplate, or fonts.

In SKILL.md:
Use the template at {baseDir}/assets/report-template.html as the report structure.
Reference the architecture diagram at {baseDir}/assets/diagram.png.


Claude sees the file path but doesn’t read the content. Instead, it might copy the template to a new location, fill in placeholders, or reference the path in generated output.

Use assets/ for HTML/CSS templates, images, binary files, configuration templates, or any file that Claude manipulates by path rather than reads into context.

The key distinction between references/ and assets/ are that


  references/: Text content loaded into Claude’s context via Read tool
  assets/: Files referenced by path only, not loaded into context


This distinction matters for context management. A 10KB markdown file in references/ consumes context tokens when loaded. A 10KB HTML template in assets/ does not. Claude just knows the path exists.


  Best practice: Always use {baseDir} for paths, never hardcode absolute paths. This makes skills portable across user environments, project directories, and different installations.


Common Skill Patterns

As with everything engineering, understanding common patterns helps in design effective skills. Here are the most useful patterns for tool integration and workflow design.

Pattern 1: Script Automation

Use case: Complex operations requiring multiple commands or deterministic logic.

This pattern offloads computational tasks to Python or Bash scripts in the scripts/ directory. The skill prompt tells Claude to execute the script and process its output.



SKILL.md example:
Run scripts/analyzer.py on the target directory:

`python {baseDir}/scripts/analyzer.py --path &quot;$USER_PATH&quot; --output report.json`

Parse the generated `report.json` and present findings.


Required tools:
allowed-tools: &quot;Bash(python {baseDir}/scripts/*:*), Read, Write&quot;


Pattern 2: Read - Process - Write

Use case: File transformation and data processing.

The simplest pattern — read input, transform it following instructions, write output. Useful for format conversions, data cleanup, or report generation.



SKILL.md example:
### Processing Workflow
1. Read input file using Read tool
2. Parse content according to format
3. Transform data following specifications
4. Write output using Write tool
5. Report completion with summary


Required tools:
allowed-tools: &quot;Read, Write&quot;


Pattern 3: Search - Analyze - Report

Use case: Codebase analysis and pattern detection.

Search the codebase for patterns using Grep, read matching files for context, analyze findings, and generate a structured report. Or, search enterprise data store for data, analyze the retrieved data for information, and generate a structured report.



SKILL.md example:
### Analysis Process
1. Use Grep to find relevant code patterns
2. Read each matched file
3. Analyze for vulnerabilities
4. Generate structured report


Required tools:
allowed-tools: &quot;Grep, Read&quot;


Pattern 4: Command Chain Execution

Use case: Multi-step operations with dependencies.

Execute a sequence of commands where each step depends on the previous one’s success. Common for CI/CD-like workflows.



SKILL.md example:
Execute analysis pipeline:
npm install &amp;amp;&amp;amp; npm run lint &amp;amp;&amp;amp; npm test

Report results from each stage.


Required tools:
allowed-tools: &quot;Bash(npm install:*), Bash(npm run:*), Read&quot;


Advanced Patterns

Wizard-Style Multi-Step Workflows

Use case: Complex processes requiring user input at each step.

Break complex tasks into discrete steps with explicit user confirmation between each phase. Useful for setup wizards, configuration tools, or guided processes.

SKILL.md example:
### Workflow

#### Step 1: Initial Setup
1. Ask user for project type
2. Validate prerequisites exist
3. Create base configuration
Wait for user confirmation before proceeding.

#### Step 2: Configuration
1. Present configuration options
2. Ask user to choose settings
3. Generate config file
Wait for user confirmation before proceeding.

#### Step 3: Initialization
1. Run initialization scripts
2. Verify setup successful
3. Report results


Template-Based Generation

Use case: Creating structured outputs from templates stored in assets/.

Load templates, fill placeholders with user-provided or generated data, and write the result. Common for report generation, boilerplate code creation, or documentation.

SKILL.md example:
### Generation Process
1. Read template from {baseDir}/assets/template.html
2. Parse user requirements
3. Fill template placeholders:
   -  → user-provided name
   -  → generated summary
   -  → current date
4. Write filled template to output file
5. Report completion


Iterative Refinement

Use case: Processes requiring multiple passes with increasing depth.

Perform broad analysis first, then progressively deeper dives on identified issues. Useful for code review, security audits, or quality analysis.

SKILL.md example:
### Iterative Analysis

#### Pass 1: Broad Scan
1. Search entire codebase for patterns
2. Identify high-level issues
3. Categorize findings

#### Pass 2: Deep Analysis
For each high-level issue:
1. Read full file context
2. Analyze root cause
3. Determine severity

#### Pass 3: Recommendation
For each finding:
1. Research best practices
2. Generate specific fix
3. Estimate effort

Present final report with all findings and recommendations.


Context Aggregation

Use case: Combining information from multiple sources to build comprehensive understanding.

Gather data from different files and tools, synthesize into a coherent picture. Useful for project summaries, dependency analysis, or impact assessments.

SKILL.md example:
### Context Gathering
1. Read project README.md for overview
2. Analyze package.json for dependencies
3. Grep codebase for specific patterns
4. Check git history for recent changes
5. Synthesize findings into coherent summary


Agent Skills Internal Architecture

With the overview and building process covered, we can now examine how skills actually work under the hood. The skills system operates through a meta-tool architecture where a tool named Skill acts as a container and dispatcher for all individual skills. This design fundamentally distinguishes skills from traditional tools in both implementation and purpose.


  The Skill tool is a meta-tool that manages all skills


Skills Object Design

Traditional tools like Read, Bash, or Write execute discrete actions and return immediate results. Skills operate differently. Rather than performing actions directly, they inject specialized instructions into the conversation history and dynamically modify Claude’s execution environment. This happens through two user messages—one containing metadata visible to users, another containing the full skill prompt hidden from the UI but sent to Claude - and by altering the agent’s context to change permissions, switch models, and adjust thinking token parameters for the duration of the skill’s use.




  
    
      Feature
      Normal Tool
      Skill Tool
    
  
  
    
      Essence
      Direct action executor
      Prompt injection + context modifier
    
    
      Message Role
      assistant → tool_useuser → tool_result
      assistant → tool_use Skilluser → tool_resultuser → skill prompt ← INJECTED!
    
    
      Complexity
      Simple (3-4 messages)
      Complex (5-10+ messages)
    
    
      Context
      Static
      Dynamic (modified per turn)
    
    
      Persistence
      Tool interactions only
      Tool interactions + skill prompts
    
    
      Token Overhead
      Minimal (~100 tokens)
      Significant (~1,500+ tokens per turn)
    
    
      Use Case
      Simple, direct tasks
      Complex, guided workflows
    
  


The complexity is substantial. Normal tools generate simple message exchanges—an assistant tool call followed by a user result. Skills inject multiple messages, operate within a dynamically modified context, and carry significant token overhead to provide the specialized instructions that guide Claude’s behavior.

Understanding how the Skill meta-tool works reveals the mechanics of this system. Let’s examine its structure:

Pd = {
  name: &quot;Skill&quot;,  // The tool name constant: $N = &quot;Skill&quot;

  inputSchema: {
    command: string  // E.g., &quot;pdf&quot;, &quot;skill-creator&quot;
  },

  outputSchema: {
    success: boolean,
    commandName: string
  },

  // 🔑 KEY FIELD: This generates the skills list
  prompt: async () =&amp;gt; fN2(),

  // Validation and execution
  validateInput: async (input, context) =&amp;gt; { /* 5 error codes */ },
  checkPermissions: async (input, context) =&amp;gt; { /* allow/deny/ask */ },
  call: async *(input, context) =&amp;gt; { /* yields messages + context modifier */ }
}


The prompt field distinguishes the Skill tool from other tools like Read or Bash, which have static descriptions. Instead of a fixed string, the Skill tool uses a dynamic prompt generator that constructs its description at runtime by aggregating the names and descriptions of all available skills. This implements progressive disclosure — the system loads only the minimal metadata (skill names and descriptions from frontmatter) into Claude’s initial context, providing just enough information for the model to decide which skill matches the user’s intent. The full skill prompt loads only after Claude makes that selection, preventing context bloat while maintaining discoverability.

async function fN2() {
  let A = await atA(),
    {
      modeCommands: B,
      limitedRegularCommands: Q
    } = vN2(A),
    G = [...B, ...Q].map((W) =&amp;gt; W.userFacingName()).join(&quot;, &quot;);
  l(`Skills and commands included in Skill tool: ${G}`);
  let Z = A.length - B.length,
    Y = nS6(B),
    J = aS6(Q, Z);
  return `Execute a skill within the main conversation

&amp;lt;skills_instructions&amp;gt;
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.

How to use skills:
- Invoke skills using this tool with the skill name only (no arguments)
- When you invoke a skill, you will see &amp;lt;command-message&amp;gt;The &quot;{name}&quot; skill is loading&amp;lt;/command-message&amp;gt;
- The skill&apos;s prompt will expand and provide detailed instructions on how to complete the task
- Examples:
  - \`command: &quot;pdf&quot;\` - invoke the pdf skill
  - \`command: &quot;xlsx&quot;\` - invoke the xlsx skill
  - \`command: &quot;ms-office-suite:pdf&quot;\` - invoke using fully qualified name

Important:
- Only use skills listed in &amp;lt;available_skills&amp;gt; below
- Do not invoke a skill that is already running
- Do not use this tool for built-in CLI commands (like /help, /clear, etc.)
&amp;lt;/skills_instructions&amp;gt;

&amp;lt;available_skills&amp;gt;
${Y}${J}
&amp;lt;/available_skills&amp;gt;
`;
}


Unlike how some tools lives in the system prompts for certain assistants such as ChatGPT, Claude agent skills do not live in the system prompt. They live in the tools array as part of the Skill tool’s description. Names of the individual skills is represented as part of the Skill meta-tool’s input schema’s command field. To better visualize how it looks, here’s the actual API request structure:

{
  &quot;model&quot;: &quot;claude-sonnet-4-5-20250929&quot;,
  &quot;system&quot;: &quot;You are Claude Code, Anthropic&apos;s official CLI...&quot;,  // ← System prompt
  &quot;messages&quot;: [
    {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Help me create a new skill&quot;},
    // ... conversation history
  ],
  &quot;tools&quot;: [  // ← Tools array sent to Claude
    {
      &quot;name&quot;: &quot;Skill&quot;,  // ← The meta-tool
      &quot;description&quot;: &quot;Execute a skill...\n\n&amp;lt;skills_instructions&amp;gt;...\n\n&amp;lt;available_skills&amp;gt;\n...&quot;,
      &quot;input_schema&quot;: {
        &quot;type&quot;: &quot;object&quot;,
        &quot;properties&quot;: {
          &quot;command&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;The skill name (no arguments)&quot;  // ← Name of individual skill
          }
        }
      }
    },
    {
      &quot;name&quot;: &quot;Bash&quot;,
      &quot;description&quot;: &quot;Execute bash commands...&quot;,
      // ...
    },
    {
      &quot;name&quot;: &quot;Read&quot;,
      // ...
    }
    // ... other tools
  ]
}


The &amp;lt;available_skills&amp;gt; section lives within the Skill tool’s description and gets regenerated for each API request. The system dynamically builds this list by aggregating currently loaded skills from user and project configurations, plugin-provided skills, and any built-in skills, subject to a token budget limit of 15,000 characters by default. This budget constraint forces skill authors to write concise descriptions and ensures the tool description doesn’t overwhelm the model’s context window.

Skill Conversation and Execution Context Injection Design

Most LLM APIs supports role: &quot;system&quot; messages that could theoretically carry system prompts. In fact, OpenAI’s ChatGPT carries its default tools in its system prompts, including bio for memory, automations for task scheduling, canmore for controlling canvas, img_gen for image generation, file_search, python, and web for Internet search. And at the end, the tools prompt takes up around 90% of the token counts in its system prompt. This could be useful but hardly efficient if we have lots of tools and/or skills to be loaded into the context.

However, system messages have different semantics that make them unsuitable for skills. System messages set global context that persists across the entire conversation, affecting all subsequent turns with higher authority than user instructions.

Skills need temporary, scoped behavior. The skill-creator skill should only affect skill creating related tasks, not transform Claude into a permanent PDF specialist for the rest of the session. Using role: &quot;user&quot; with isMeta: true makes the skill prompt appear as user input to Claude, keeping it temporary and localized to the current interaction. After the skill completes, the conversation returns to normal conversation context and execution context without residual behavioral modifications.

Normal tools like Read, Write, or Bash have simple communication patterns. When Claude invokes Read, it sends a file path, receives the file contents, and continues working. The user sees “Claude used the Read tool” in their transcript, and that’s sufficient transparency. The tool did one thing, returned a result, and that’s the end of the interaction. Skills operate fundamentally differently. Instead of executing discrete actions and returning results, skills inject comprehensive instruction sets that modify how Claude reasons about and approaches the task. This creates a design challenge that normal tools never face: users need transparency about which skills are running and what they’re doing, while Claude needs detailed, potentially verbose instructions to execute the skill correctly. If users see the full skill prompts in their chat transcript, the UI becomes cluttered with thousands of words of internal AI instructions. If the skill activation is completely hidden, users lose visibility into what the system is doing on their behalf. The solution requires separating these two communication channels into distinct messages with different visibility rules.

The skills system uses an isMeta flag on each message to control whether it appears in the user interface. When isMeta: false (or when the flag is omitted and defaults to false), the message renders in the conversation transcript that users see. When isMeta: true, the message gets sent to the Anthropic API as part of Claude’s conversation context but never appears in the UI. This simple boolean flag enables sophisticated dual-channel communication: one stream for human users, another for the AI model. Meta-prompting for meta-tools!

When a skill executes, the system injects two separate user messages into the conversation history. The first carries skill metadata with isMeta: false, making it visible to users as a status indicator. The second carries the full skill prompt with isMeta: true, hiding it from the UI while making it available to Claude. This split solves the transparency vs clarity tradeoff by showing users what’s happening without overwhelming them with implementation details.

The metadata message uses a concise XML structure that the frontend can parse and display appropriately:

let metadata = [
  `&amp;lt;command-message&amp;gt;${statusMessage}&amp;lt;/command-message&amp;gt;`,
  `&amp;lt;command-name&amp;gt;${skillName}&amp;lt;/command-name&amp;gt;`,
  args ? `&amp;lt;command-args&amp;gt;${args}&amp;lt;/command-args&amp;gt;` : null
].filter(Boolean).join(&apos;\n&apos;);

// Message 1: NO isMeta flag → defaults to false → VISIBLE
messages.push({
  content: metadata,
  autocheckpoint: checkpointFlag
});


When the PDF skill activates, for example, users see a clean loading indicator in their transcript:
&amp;lt;command-message&amp;gt;The &quot;pdf&quot; skill is loading&amp;lt;/command-message&amp;gt;
&amp;lt;command-name&amp;gt;pdf&amp;lt;/command-name&amp;gt;
&amp;lt;command-args&amp;gt;report.pdf&amp;lt;/command-args&amp;gt;


This message stays intentionally minimal - typically 50 to 200 characters. The XML tags enable the frontend to render it with special formatting, validate that proper &amp;lt;command-message&amp;gt; tags are present, and maintain an audit trail of which skills executed during the session. Because the isMeta flag defaults to false when omitted, this metadata automatically appears in the UI.

The skill prompt message takes the opposite approach. It loads the full content from SKILL.md, potentially augments it with additional context, and explicitly sets isMeta: true to hide it from users:

let skillPrompt = await skill.getPromptForCommand(args, context);

// Augment with prepend/append content if needed
let fullPrompt = prependContent.length &amp;gt; 0 || appendContent.length &amp;gt; 0
  ? [...prependContent, ...appendContent, ...skillPrompt]
  : skillPrompt;

// Message 2: Explicit isMeta: true → HIDDEN
messages.push({
  content: fullPrompt,
  isMeta: true  // HIDDEN FROM UI, SENT TO API
});


A typical skill prompt runs 500 to 5,000 words and provides comprehensive guidance to transform Claude’s behavior. The PDF skill prompt might contain:

You are a PDF processing specialist.

Your task is to extract text from PDF documents using the pdftotext tool.

### Process

1. Validate the PDF file exists
2. Run pdftotext command to extract text
3. Read the output file
4. Present the extracted text to the user

### Tools Available

You have access to:
- Bash(pdftotext:*) - For running pdftotext command
- Read - For reading extracted text
- Write - For saving results if needed

### Output Format

Present the extracted text clearly formatted.

Base directory: /path/to/skill
User arguments: report.pdf


This prompt establishes task context, outlines the workflow, specifies available tools, defines output format, and provides environment-specific paths. The markdown structure with headers, lists, and code blocks helps Claude parse and follow the instructions. With isMeta: true, this entire prompt gets sent to the API but never clutters the user’s transcript.

Beyond the core metadata and skill prompt, skills can inject additional conditional messages for attachments and permissions:

let allMessages = [
  createMessage({ content: metadata, autocheckpoint: flag }),  // 1. Metadata
  createMessage({ content: skillPrompt, isMeta: true }),       // 2. Skill prompt
  ...attachmentMessages,                                       // 3. Attachments (conditional)
  ...(allowedTools.length || skill.model ? [
    createPermissionsMessage({                                 // 4. Permissions (conditional)
      type: &quot;command_permissions&quot;,
      allowedTools: allowedTools,
      model: skill.useSmallFastModel ? getFastModel() : skill.model
    })
  ] : [])
];


Attachment messages can carry diagnostics information, file references, or additional context that supplements the skill prompt. Permission messages only appear when the skill specifies allowed-tools in its frontmatter or requests a model override, providing metadata that modifies the runtime execution environment. This modular composition allows each message to have a specific purpose and be included or excluded based on the skill’s configuration, extending the basic two-message pattern to handle more complex scenarios while maintaining the same visibility control through isMeta flags.

Why Two Messages Instead of One?

A single-message design would force an impossible choice. Setting isMeta: false would make the entire message visible, dumping thousands of words of AI instructions into the user’s chat transcript. Users would see something like:

┌─────────────────────────────────────────────┐
│ The &quot;pdf&quot; skill is loading                  │
│                                             │
│ You are a PDF processing specialist.        │
│                                             │
│ Your task is to extract text from PDF       │
│ documents using the pdftotext tool.         │
│                                             │
│ ## Process                                  │
│                                             │
│ 1. Validate the PDF file exists             │
│ 2. Run pdftotext command to extract text    │
│ 3. Read the output file                     │
│ ... [500 more lines] ...                    │
└─────────────────────────────────────────────┘


The UI becomes unusable, filled with internal implementation details meant for Claude, not humans. Alternatively, setting isMeta: true would hide everything, providing no transparency about which skill activated or what arguments it received. Users would have no visibility into what the system is doing on their behalf.

The two-message split resolves this by giving each message a different isMeta value. Message 1 with isMeta: false provides user-facing transparency. Message 2 with isMeta: true provides Claude with detailed instructions. This granular control enables transparency without information overload.

The messages also serve fundamentally different audiences and purposes:


  
    
      Aspect
      Metadata Message
      Skill Prompt Message
    
  
  
    
      Audience
      Human user
      Claude (AI)
    
    
      Purpose
      Status/transparency
      Instructions/guidance
    
    
      Length
      ~50-200 chars
      ~500-5,000 words
    
    
      Format
      Structured XML
      Natural language markdown
    
    
      Visibility
      Should be visible
      Should be hidden
    
    
      Content
      “What is happening?”
      “How to do it?”
    
  


The codebase even processes these messages through different paths. The metadata message gets parsed for &amp;lt;command-message&amp;gt; tags, validated, and formatted for UI display. The skill prompt message gets sent directly to the API without parsing or validation—it’s raw instructional content meant only for Claude’s reasoning process. Combining them would violate the Single Responsibility Principle by forcing one message to serve two distinct audiences through two different processing pipelines.

Case Study: Execution Lifecycle

Now covered Agent Skills internal architecture, let’s walk through what happens when a user says “Extract text from report.pdf” by examining the complete execution flow using a hypothetical pdf skill as a case study.



Phase 1: Discovery &amp;amp; Loading (Startup)

When Claude Code starts, it scans for skills:

async function getAllCommands() {
  // Load from all sources in parallel
  let [userCommands, skillsAndPlugins, pluginCommands, builtins] =
    await Promise.all([
      loadUserCommands(),      // ~/.claude/commands/
      loadSkills(),            // .claude/skills/ + plugins
      loadPluginCommands(),    // Plugin-defined commands
      getBuiltinCommands()     // Hardcoded commands
    ]);

  return [...userCommands, ...skillsAndPlugins, ...pluginCommands, ...builtins]
    .filter(cmd =&amp;gt; cmd.isEnabled());
}

// Specific skill loading
async function loadPluginSkills(plugin) {
  // Check if plugin has skills
  if (!plugin.skillsPath) return [];

  // Two patterns supported:
  // 1. Root SKILL.md in skillsPath
  // 2. Subdirectories with SKILL.md

  const skillFiles = findSkillMdFiles(plugin.skillsPath);
  const skills = [];

  for (const file of skillFiles) {
    const content = readFile(file);
    const { frontmatter, markdown } = parseFrontmatter(content);

    skills.push({
      type: &quot;prompt&quot;,
      name: `${plugin.name}:${getSkillName(file)}`,
      description: `${frontmatter.description} (plugin:${plugin.name})`,
      whenToUse: frontmatter.when_to_use,  // ← Note: underscores!
      allowedTools: parseTools(frontmatter[&apos;allowed-tools&apos;]),
      model: frontmatter.model === &quot;inherit&quot; ? undefined : frontmatter.model,
      isSkill: true,
      promptContent: markdown,
      // ... other fields
    });
  }

  return skills;
}


For the pdf skill, this produces:
{
  type: &quot;prompt&quot;,
  name: &quot;pdf&quot;,
  description: &quot;Extract text from PDF documents (plugin:document-tools)&quot;,
  whenToUse: &quot;When user wants to extract or process text from PDF files&quot;,
  allowedTools: [&quot;Bash(pdftotext:*)&quot;, &quot;Read&quot;, &quot;Write&quot;],
  model: undefined,  // Uses session model
  isSkill: true,
  disableModelInvocation: false,
  promptContent: &quot;You are a PDF processing specialist...&quot;,
  // ... other fields
}


Phase 2: Turn 1 - User Request &amp;amp; Skill Selection

The user sends a request: “Extract text from report.pdf”. Claude receives this message along with the Skill tool in its tools array. Before Claude can decide to invoke the pdf skill, the system must present available skills in the Skill tool’s description.

Skill Filtering &amp;amp; Presentation

Not all loaded skills appear in the Skill tool. A skill MUST have either description OR when_to_use in frontmatter, or it’s filtered out. Filtering criteria:

async function getSkillsForSkillTool() {
  const allCommands = await getAllCommands();

  return allCommands.filter(cmd =&amp;gt;
    cmd.type === &quot;prompt&quot; &amp;amp;&amp;amp;
    cmd.isSkill === true &amp;amp;&amp;amp;
    !cmd.disableModelInvocation &amp;amp;&amp;amp;
    (cmd.source !== &quot;builtin&quot; || cmd.isModeCommand === true) &amp;amp;&amp;amp;
    (cmd.hasUserSpecifiedDescription || cmd.whenToUse)  // ← Must have one!
  );
}

Skill Formatting

Each skill is formatted for the &amp;lt;available_skills&amp;gt; section. As an example, our hypothetical pdf skill could be formatted into 
&quot;pdf&quot;: Extract text from PDF documents - When user wants to extract or process text from PDF files

function formatSkill(skill) {
  let name = skill.name;
  let description = skill.whenToUse
    ? `${skill.description} - ${skill.whenToUse}`
    : skill.description;

  return `&quot;${name}&quot;: ${description}`;
}


Claude’s Decision Process

Now, when user prompts: “Extract text from report.pdf”. Claude receives the API request with the Skill tool, reads the &amp;lt;available_skills&amp;gt;, and reasons (hypothetically, as we do not see reasoning traces):

Internal reasoning:
- User wants to &quot;extract text from report.pdf&quot;
- This is a PDF processing task
- Looking at available skills...
- &quot;pdf&quot;: Extract text from PDF documents - When user wants to extract or process text from PDF files
- This matches! The user wants to extract text from a PDF
- Decision: Invoke Skill tool with command=&quot;pdf&quot;


Note that there’s no algorithmic matching here. No lexical matching. No semantic matching. No searches. This is pure LLM reasoning for its decisions based on the description of the skill. Once done, Claude returns a tool use:

{
  &quot;type&quot;: &quot;tool_use&quot;,
  &quot;id&quot;: &quot;toolu_123abc&quot;,
  &quot;name&quot;: &quot;Skill&quot;,
  &quot;input&quot;: {
    &quot;command&quot;: &quot;pdf&quot;
  }
}


Phase 3: Skill Tool Execution

The Skill tool now executes. This corresponds to the yellow “SKILL TOOL EXECUTION” box in the sequence diagram, which performs validation, permission checks, file loading, and context modification before yielding the result.

Step 1: Validation

async validateInput({ command }, context) {
  let skillName = command.trim().replace(/^\//, &quot;&quot;);

  // Error 1: Empty
  if (!skillName) return { result: false, errorCode: 1 };

  // Error 2: Unknown skill
  const allSkills = await getAllCommands();
  if (!skillExists(skillName, allSkills)) {
    return { result: false, errorCode: 2 };
  }

  // Error 3: Can&apos;t load
  const skill = getSkill(skillName, allSkills);
  if (!skill) return { result: false, errorCode: 3 };

  // Error 4: Model invocation disabled
  if (skill.disableModelInvocation) {
    return { result: false, errorCode: 4 };
  }

  // Error 5: Not prompt-based
  if (skill.type !== &quot;prompt&quot;) {
    return { result: false, errorCode: 5 };
  }

  return { result: true };
}


The pdf skill passes all validation checks ✓

Step 2: Permission Check

async checkPermissions({ command }, context) {
  const skillName = command.trim().replace(/^\//, &quot;&quot;);
  const permContext = (await context.getAppState()).toolPermissionContext;

  // Check deny rules
  for (const [pattern, rule] of getDenyRules(permContext)) {
    if (matches(skillName, pattern)) {
      return { behavior: &quot;deny&quot;, message: &quot;Blocked by permission rules&quot; };
    }
  }

  // Check allow rules
  for (const [pattern, rule] of getAllowRules(permContext)) {
    if (matches(skillName, pattern)) {
      return { behavior: &quot;allow&quot; };
    }
  }

  // Default: ask user
  return { behavior: &quot;ask&quot;, message: `Execute skill: ${skillName}` };
}


Assuming no rules, user is prompted: “Execute skill: pdf?”
User approves ✓

Step 3: Load Skill File and Generate Execution Context Modification

With validation and permissions approved, the Skill tool loads the skill file and prepares the execution context modification:

async *call({ command }, context) {
  const skillName = command.trim().replace(/^\//, &quot;&quot;);
  const allSkills = await getAllCommands();
  const skill = getSkill(skillName, allSkills);

  // Load the skill prompt
  const promptContent = await skill.getPromptForCommand(&quot;&quot;, context);

  // Generate metadata tags
  const metadata = [
    `&amp;lt;command-message&amp;gt;The &quot;${skill.userFacingName()}&quot; skill is loading&amp;lt;/command-message&amp;gt;`,
    `&amp;lt;command-name&amp;gt;${skill.userFacingName()}&amp;lt;/command-name&amp;gt;`
  ].join(&apos;\n&apos;);

  // Create messages
  const messages = [
    { type: &quot;user&quot;, content: metadata },  // Visible to user
    { type: &quot;user&quot;, content: promptContent, isMeta: true },  // Hidden from user, visible to Claude
    // ... attachments, permissions
  ];

  // Extract configuration
  const allowedTools = skill.allowedTools || [];
  const modelOverride = skill.model;

  // Yield result with execution context modifier
  yield {
    type: &quot;result&quot;,
    data: { success: true, commandName: skillName },
    newMessages: messages,

    // 🔑 Execution context modification function
    contextModifier(context) {
      let modified = context;

      // Inject allowed tools
      if (allowedTools.length &amp;gt; 0) {
        modified = {
          ...modified,
          async getAppState() {
            const state = await context.getAppState();
            return {
              ...state,
              toolPermissionContext: {
                ...state.toolPermissionContext,
                alwaysAllowRules: {
                  ...state.toolPermissionContext.alwaysAllowRules,
                  command: [
                    ...state.toolPermissionContext.alwaysAllowRules.command || [],
                    ...allowedTools  // ← Pre-approve these tools
                  ]
                }
              }
            };
          }
        };
      }

      // Override model
      if (modelOverride) {
        modified = {
          ...modified,
          options: {
            ...modified.options,
            mainLoopModel: modelOverride
          }
        };
      }

      return modified;
    }
  };
}


The Skill tool yields its result containing newMessages (metadata + skill prompt + permissions for conversation context injection) and contextModifier (tool permissions + model override for execution context modification). This completes the yellow “SKILL TOOL EXECUTION” box from the sequence diagram.

Phase 4: Send to API (Turn 1 Completion)

The system constructs the complete messages array to send to the Anthropic API. This includes all messages from the conversation plus the newly injected skill messages:

// Complete message array sent to API for Turn 1
{
  model: &quot;claude-sonnet-4-5-20250929&quot;,
  messages: [
    {
      role: &quot;user&quot;,
      content: &quot;Extract text from report.pdf&quot;
    },
    {
      role: &quot;assistant&quot;,
      content: [
        {
          type: &quot;tool_use&quot;,
          id: &quot;toolu_123abc&quot;,
          name: &quot;Skill&quot;,
          input: { command: &quot;pdf&quot; }
        }
      ]
    },
    {
      role: &quot;user&quot;,
      content: &quot;&amp;lt;command-message&amp;gt;The \&quot;pdf\&quot; skill is loading&amp;lt;/command-message&amp;gt;\n&amp;lt;command-name&amp;gt;pdf&amp;lt;/command-name&amp;gt;&quot;
      // isMeta: false (default) - VISIBLE to user in UI
    },
    {
      role: &quot;user&quot;,
      content: &quot;You are a PDF processing specialist...\n\n## Process\n1. Validate PDF exists\n2. Run pdftotext...&quot;,
      isMeta: true  // HIDDEN from UI, sent to API
    },
    {
      role: &quot;user&quot;,
      content: {
        type: &quot;command_permissions&quot;,
        allowedTools: [&quot;Bash(pdftotext:*)&quot;, &quot;Read&quot;, &quot;Write&quot;],
        model: undefined
      }
    }
  ]
}




The above shows what we have done up to this point. The execution context modifier is applied, pre-approving Bash(pdftotext:*), Read, and Write for subsequent tool calls. The request is sent to the Anthropic API. This concludes the skill execution. If this is a normal tool, we are all done. However, skills are different. Agent skill has only injected the conversation context and execution contexts. THis means we still need to invoke Claude agent with all these injected contexts to complete user’s request!

Phase 5: Bash Tool Execution (Tool use with Skill Context!)

Claude receives the API response with the injected conversation context. The skill prompt has transformed Claude’s behavior, providing it with:

  Specialized PDF processing instructions (conversation context)
  Pre-approved access to Bash(pdftotext:*), Read, and Write tools (execution context)
  A clear workflow to follow (conversation context)


Claude processes the context and follows the pdf skill’s workflow:

I&apos;ll extract text from report.pdf. Let me process the file.

[Following pdf skill&apos;s instructions]
1. Validate that report.pdf exists
2. Run pdftotext command to extract text
3. Read the output file
4. Present the extracted text to you


Claude uses the Bash tool (pre-approved, no user prompt required):

{
  &quot;type&quot;: &quot;tool_use&quot;,
  &quot;id&quot;: &quot;toolu_456def&quot;,
  &quot;name&quot;: &quot;Bash&quot;,
  &quot;input&quot;: {
    &quot;command&quot;: &quot;pdftotext report.pdf output.txt&quot;,
    &quot;description&quot;: &quot;Extract text from PDF using pdftotext&quot;
  }
}


The Bash tool executes successfully, returning the result. Claude then reads the output file using the Read tool and presents the extracted text to the user. The skill has successfully guided Claude through the specialized PDF extraction workflow by injecting instructions into the conversation context and modifying the execution context for tool permissions.



Conclusion: The Mental Model Recap

Skills in Claude Code are prompt-based conversation and execution context modifiers that work through a meta-tool architecture:

Key Takeaways:


  Skills are prompt templates in SKILL.md files, not executable code
  The Skill tool (capital S) is a meta-tool in the tools array that manages individual skills, not in the system prompt
  Skills modify conversation context by injecting instruction prompts (via isMeta: true messages)
  Skills modify execution context by changing tool permissions and model selection
  Selection happens via LLM reasoning, not algorithmic matching
  Tool permissions are scoped to skill execution via execution context modification
  Skills inject two user messages per invocation—one for user-visible metadata, one for hidden instructions sent to the API


The Elegant Design: By treating specialized knowledge as prompts that modify conversation context and permissions that modify execution context rather than code that executes, Claude Code achieves flexibility, safety, and composability that would be difficult with traditional function calling.



References


  Introducing Agent Skills
  Equipping Agents for the Real World with Agent Skills
  Claude Code Documentation
  Anthropic API Reference
  Official Documented Frontmatter Fields
  Internal Comms Skill
  Skill Creator Skill
  ChatGPT 5 System Prompt (leaked, not official)


@article{
    leehanchung_bullshit_jobs,
    author = {Lee, Hanchung},
    title = {Claude Agent Skills: A First Principles Deep Dive},
    year = {2025},
    month = {10},
    day = {26},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/}
}



        </description>

        <pubDate>Sun, 26 Oct 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/10/26/claude-skills-deep-dive/</guid>

      </item>

    

      <item>

        <title>Building for IT 3.0: Why Agent-Native Products Will Crush SaaS Click-Ops</title>

        <description>

          Product strategy for the shift from human interfaces to AI reasoning engines - 

          If you’re building B2B software, your product strategy needs to account for a fundamental shift happening right now. The transition from IT 2.0 (SaaS) to IT 3.0 (AI-native systems) isn’t just changing how enterprises operate—it’s rewriting the rules of what makes products valuable and defensible.

In my previous article on AI transformation and the end of bullshit jobs, I covered the organizational implications of this shift. This article focuses on what it means for builders: what to build, how to build it, and where competitive moats emerge when AI agents replace human operators.

The Three Eras of Enterprise IT: A Product Lens

Understanding the evolution reveals what’s about to be disrupted.

IT 1.0: Custom In-House Software

  Products: Bespoke systems from vendors like IBM, Oracle, SAP
  Buyer: IT departments with capital budgets
  Moat: Implementation complexity, customization depth, vendor lock-in
  Jobs created: Programmers, system operators, integration specialists


IT 2.0: SaaS and Modular Tools

  Products: Cloud-delivered tools (Salesforce, Workday, HubSpot)
  Buyer: Departmental leaders with operating budgets
  Moat: Network effects, switching costs, ecosystem lock-in
  Jobs created: SaaS admins, implementation engineers, ops coordinators, system integrators


The IT 2.0 explosion created hundreds of specialized tools, each with its own interface, data model, and workflow. This fragmentation spawned an entire ecosystem of “glue work” jobs—people whose careers existed to reconcile systems that couldn’t talk to each other.


IT 3.0: AI-Native Systems

  Products: Agent platforms, reasoning engines, outcome delivery systems
  Buyer: Executives seeking velocity and outcomes
  Moat: ??? (This is what we’re here to figure out)
  Jobs created: Platform engineers, AI architects, exception handlers, model specialists


The critical insight: IT 3.0 products are fundamentally different from SaaS. You’re not building tools for humans to operate. You’re building environments for AI agents to deliver outcomes.

If your product roadmap still centers on “better UI/UX” and “more integrations,” you’re optimizing for a paradigm that’s already dying.

Why Traditional SaaS Moats Are Collapsing

The barriers that protected SaaS businesses are evaporating in IT 3.0:

Switching costs disappear. IT 2.0 switching costs came from:

  Training hundreds of users on complex interfaces
  Migrating data between rigid schemas
  Rebuilding integrations and custom workflows
  Change management across the organization


IT 3.0 collapses these barriers. An AI agent doesn’t need training. Data migration becomes a reasoning task, not a manual mapping project. Workflow “migration” means changing instructions, not retraining staff. When switching costs approach zero, retention requires different strategies.

Implementation complexity was a moat. SaaS vendors benefited from complex setup that required professional services, consultants, and months of configuration. The harder your product was to implement, the less likely customers were to rip it out.

AI inverts this. Complexity in setup is now a liability, not an asset. When your competitor can deliver value in minutes while you require months, your “deep customization” becomes dead weight.

Network effects weaken. IT 2.0 network effects came from user ecosystems, third-party integrations, and accumulated platform data. If agents can operate across products, being “the platform where everyone lives” matters less. An agent can use more than one CRM.

User lock-in through training evaporates. You spent months training your sales team on Salesforce. That investment locked you in. In IT 3.0, the AI agent is trained on your behalf. Switching means new prompts, not new training programs.

So if traditional moats are collapsing, what creates defensibility in IT 3.0?

The New Product Moats: Speed, Context, and Reasoning

Three factors create competitive advantage when implementation barriers disappear:

1. Time to Utility

Previously I’ve written about how enterprise software moats are defined by Cost to Implement, Time to Implement, and Time to Utility. In IT 3.0, the first two collapse to near-zero. Time to Utility becomes the primary moat.

Time to Utility: How long from “we signed the contract” to “we’re getting measurable value”?


  IT 1.0: Months to years (custom development, deployment)
  IT 2.0: Weeks to months (onboarding, training, configuration)
  IT 3.0: Minutes to days (instruction, context loading, outcome delivery)


Products that deliver utility faster win. This isn’t about “easier onboarding”—it’s about fundamentally different architecture that eliminates setup entirely.

2. Context Depth and Quality

AI reasoning engines are only as good as the context they operate with. This creates a new moat: proprietary context that improves AI reasoning.

Unlike IT 2.0 where data lived in silos, IT 3.0 agents need comprehensive cross-functional context to reason effectively. Products that accumulate and provide this context have defensibility.

This means:

  Historical decision rationale, not just transaction logs
  Relationship graphs between entities
  Temporal context (why things changed over time)
  Cross-functional workflows (how sales, legal, finance interact)


The moat isn’t “we have data”—everyone has data. The moat is “our data is structured for AI reasoning, and we’ve accumulated context that would take competitors years to replicate.”

3. Reasoning Quality and Reliability

When outcomes replace tools, quality of reasoning becomes the differentiator. This creates moats around:

Domain-specific model performance. General models handle general tasks. Domain-specific models fine-tuned on industry workflows handle specialized reasoning better. If you’re building for legal tech, healthcare, financial services—model specialization becomes a moat.

Error rate and reliability. In IT 2.0, users could catch software errors. In IT 3.0, when agents operate autonomously, error rates directly impact business outcomes. Products with lower error rates and better guardrails win.

Explainability and trust. Enterprises need to understand why an AI made a decision, especially in regulated industries. Products that provide reasoning transparency have an edge.

Agent-Native vs. Human-Native Product Design

The fundamental architectural shift: you’re not designing for human operators anymore.

Human-Native Design (IT 2.0)


  Interface: Web UI, mobile apps, dashboards
  Workflow: Click through forms, review and approve, manually trigger actions
  Data model: Optimized for human comprehension (customer records, deal stages)
  Integration: APIs designed for front-ends to consume
  Value delivery: Tool enables human to do work


Agent-Native Design (IT 3.0)


  Interface: Natural language, APIs designed for reasoning
  Workflow: Describe outcome, agent determines steps autonomously
  Data model: Optimized for reasoning context (relationships, rationale, temporal changes)
  Integration: APIs expose reasoning context, not just CRUD operations
  Value delivery: System delivers outcome directly


Concrete example: Contract review

IT 2.0 approach (human-native):

  User uploads contract PDF to platform
  Platform extracts text and displays in UI
  User reviews clauses highlighted by simple rules
  User manually flags issues in comments
  User exports annotated PDF
  User emails to legal team for approval


Value: Tool made review slightly faster than reading in Word.

IT 3.0 approach (agent-native):

  User (or another agent) sends contract with instruction: “Review for compliance with our standard terms, flag risks, negotiate better payment terms”
  Agent reads contract, accesses company policy context, compares to past contracts
  Agent identifies 3 compliance issues, 2 negotiation opportunities
  Agent drafts redlines with rationale
  Agent routes to appropriate approver based on risk threshold
  If approved, agent sends to counterparty with negotiation points


Value: Outcome delivered (compliant, negotiated contract) not tool provided.

The difference isn’t “better automation”—it’s fundamentally rethinking what the product does.

Service as Software: Delivering Outcomes, Not Tools

I’ve previously written about Service as Software—the idea that AI enables software to deliver outcomes that previously required service professionals.

This concept becomes the core product strategy for IT 3.0:

IT 2.0 SaaS: Subscribe to tools, hire people to operate them
IT 3.0 Service as Software: Subscribe to outcomes, AI agents do the work

Examples of the shift:


  
    
      Traditional SaaS
      Service as Software
    
  
  
    
      CRM tool
      Lead qualification service
    
    
      ATS platform
      Recruiting coordination service
    
    
      Project management tool
      Project execution service
    
    
      Analytics dashboard
      Strategic insights service
    
    
      Document management
      Contract negotiation service
    
  


The strategic implications:

Pricing shifts from seats to outcomes. You don’t charge per user if users aren’t operating the system. You charge for:

  Outcomes delivered (contracts processed, candidates screened, deals qualified)
  Value created (revenue generated, costs saved, risks mitigated)
  Capacity provided (equivalent to X human hours of work)


Product definition changes. Instead of “what features do we build?” the question becomes “what outcomes can we reliably deliver?”

This requires:

  Defining success metrics for each outcome
  Building reliability and error handling for autonomous operation
  Creating appropriate guardrails and human-in-the-loop triggers
  Measuring quality of outcomes, not quality of UI


Customer expectations evolve. In IT 2.0, customers expected a tool that “works” (doesn’t crash, has features). In IT 3.0, customers expect outcomes that “work” (achieve business goals reliably).

Your product needs to be measured on business impact, not feature completeness.

API Design for AI Agents: Beyond CRUD

Traditional REST APIs were designed for front-ends to consume. Agent-native APIs need different primitives.

What Traditional APIs Provide

CRUD operations:
GET /customers/123
POST /customers
PUT /customers/123
DELETE /customers/123


This works when humans drive workflows through UIs. It fails when agents need to reason about why something should happen.

What Agent-Native APIs Need

1. Context-Rich Endpoints

Agents need reasoning context, not just current state:
GET /customers/123/context
{
  &quot;customer&quot;: {...},
  &quot;interaction_history&quot;: [...],
  &quot;preferences&quot;: {...},
  &quot;risk_factors&quot;: [...],
  &quot;related_entities&quot;: {
    &quot;contracts&quot;: [...],
    &quot;opportunities&quot;: [...],
    &quot;support_tickets&quot;: [...]
  },
  &quot;decision_history&quot;: [
    {
      &quot;decision&quot;: &quot;approved_credit_increase&quot;,
      &quot;rationale&quot;: &quot;...&quot;,
      &quot;outcome&quot;: &quot;...&quot;,
      &quot;timestamp&quot;: &quot;...&quot;
    }
  ]
}


2. Outcome-Oriented Operations

Instead of “create a record,” agents need “achieve an outcome”:
POST /outcomes/qualify-lead
{
  &quot;lead_id&quot;: &quot;...&quot;,
  &quot;qualification_criteria&quot;: {...},
  &quot;context&quot;: {...}
}

Response:
{
  &quot;qualified&quot;: true,
  &quot;confidence&quot;: 0.87,
  &quot;reasoning&quot;: &quot;...&quot;,
  &quot;next_actions&quot;: [...]
}


3. Reasoning Transparency

Agents need to explain their work:
GET /decisions/456/reasoning
{
  &quot;decision&quot;: &quot;rejected_contract&quot;,
  &quot;factors&quot;: [
    {
      &quot;factor&quot;: &quot;payment_terms&quot;,
      &quot;weight&quot;: 0.4,
      &quot;evaluation&quot;: &quot;unfavorable&quot;,
      &quot;details&quot;: &quot;Net 90 exceeds policy threshold of Net 60&quot;
    }
  ],
  &quot;similar_precedents&quot;: [...],
  &quot;override_process&quot;: &quot;...&quot;
}


4. Temporal and Relational Context

GET /entities/customer-123/relationships
{
  &quot;relationships&quot;: [
    {
      &quot;entity_type&quot;: &quot;opportunity&quot;,
      &quot;entity_id&quot;: &quot;opp-456&quot;,
      &quot;relationship_type&quot;: &quot;current_deal&quot;,
      &quot;strength&quot;: &quot;strong&quot;,
      &quot;temporal_context&quot;: {
        &quot;created&quot;: &quot;...&quot;,
        &quot;milestones&quot;: [...],
        &quot;status_changes&quot;: [...]
      }
    }
  ]
}


The shift: from “fetch data for display” to “provide context for reasoning.”

If your API documentation still looks like Stripe’s (excellent for developers, useless for agents), you’re building for the wrong consumer.

Which SaaS Categories Are Most Vulnerable?

Not all SaaS is equally at risk. AI hits hardest where products created clerical overhead and workflow rigidity.

Highly Vulnerable: Glue Work Tools

CRM (Salesforce, HubSpot)

  Why vulnerable: Data entry, pipeline updates, report generation—all automatable
  Attack vector: Agent-native CRM that auto-updates based on conversations, auto-scores leads, auto-generates insights
  Timeline: 18-24 months before viable replacements gain traction
  Defensibility that remains: Deep enterprise integrations, but weakening fast


ATS &amp;amp; HR Platforms (Workday, Greenhouse)

  Why vulnerable: Resume parsing, candidate scheduling, workflow coordination—trivial for AI
  Attack vector: Recruiting agents that source, screen, schedule, and coordinate autonomously
  Timeline: 12-18 months
  Defensibility that remains: Compliance/audit trails, but becoming commoditized


Project Management (Asana, Jira, Monday)

  Why vulnerable: Task creation, updates, cross-tool syncing—classic glue work
  Attack vector: Project execution agents that manage work, not just track it
  Timeline: 24-36 months (harder problem, requires cross-team coordination)
  Defensibility that remains: Team collaboration features, but diminishing as async AI work increases


Customer Support (Zendesk, ServiceNow)

  Why vulnerable: Tier-1 support already being automated, ticket routing is trivial
  Attack vector: Support agents that resolve issues autonomously, escalate intelligently
  Timeline: 6-12 months (already happening)
  Defensibility that remains: Complex escalation workflows, enterprise ticketing


Moderately Vulnerable: Specialized Workflows

Finance/ERP (NetSuite, SAP)

  Why vulnerable: Invoice matching, categorization, reporting—all automatable
  Less vulnerable: Deep regulatory/compliance requirements, complex approval chains
  Timeline: 36-48 months (slower due to risk aversion in finance)


Marketing Automation (Marketo, Pardot)

  Why vulnerable: Campaign execution, lead scoring—automatable
  Less vulnerable: Creative strategy, brand decisions still require humans
  Timeline: 18-24 months


Less Vulnerable: Creative and Strategic Tools

Design Tools (Figma, Adobe)

  Less vulnerable: Creative work still requires human taste and judgment
  Vulnerable components: Asset creation, template generation
  Timeline: 48+ months for full disruption


BI/Analytics (Tableau, Looker)

  Less vulnerable: Strategic insights require business context
  Vulnerable components: Report generation, data prep
  Timeline: 24-36 months


The pattern: tools that enabled humans to do clerical work get disrupted first. Tools that augment creative/strategic work transform more slowly.

Building Defensibility in IT 3.0

When implementation barriers collapse, what creates lock-in?

1. Own the Model Layer

The Model is the Product. Generalist models (GPT-4, Claude) handle general tasks. Domain-specific models fine-tuned on your data handle specialized reasoning better.

If you’re building legal tech, your contract analysis model trained on 10 million real contracts has a moat. A competitor using vanilla GPT-4 can’t match your domain expertise.

This requires:

  Building proprietary training datasets
  Investing in model fine-tuning and evaluation
  Continuously improving model performance
  Owning the feedback loop (outcomes → model improvement)


The trap: Thinking you can build everything on top of GPT/Claude APIs without proprietary models. You can—but you won’t have defensibility. As soon as OpenAI or Anthropic adds your feature to their API, you’re done.

2. Accumulate Reasoning Context

Unlike IT 2.0 where data silos were a bug, in IT 3.0, accumulated context is a feature.

Context moats:

  Historical decision rationale (why things happened)
  Cross-functional relationships (how entities connect)
  Temporal patterns (what changed when and why)
  Domain-specific knowledge graphs


The longer a customer uses your product, the better your AI reasons about their specific business. This creates switching costs even when implementation costs are zero.

Example: Legal AI that knows your company’s negotiation history, risk tolerances, and precedent decisions becomes exponentially more valuable over time. A competitor might offer “better AI,” but they don’t have your context.

3. Deliver Measurable Outcomes

In IT 2.0, “success” meant users adopted your tool. In IT 3.0, success means measurable business outcomes.

Products that can prove ROI through clear outcome metrics have defensibility:

  Contracts processed per month (legal AI)
  Deals qualified accurately (sales AI)
  Support tickets resolved without escalation (support AI)
  Audit compliance rate (finance AI)


This requires:

  Defining clear success metrics with customers
  Building measurement into the product
  Optimizing for outcomes, not features
  Pricing based on value delivered


When your product demonstrably saves $500K/year in legal costs, price becomes less sensitive.

4. Build Distribution, Not Just Technology

Distribution is the moat. When technology becomes commoditized (every AI vendor has access to similar models), winning means reaching customers first and locking in usage.

Distribution strategies:

  Embedded in existing workflows (agents that live in Slack, email, existing tools)
  Partnerships with incumbent platforms (better to be inside Salesforce than compete with it)
  Vertical-specific go-to-market (own legal AI, healthcare AI, financial services AI—not “general AI”)
  Developer ecosystems (if other builders use your agent platform, you win by default)


The mistake: Building the “best” AI product and expecting customers to find you. In IT 3.0, distribution matters more than ever because switching costs are low.

5. Reliability and Trust

When AI operates autonomously, errors compound. Products with lower error rates and better guardrails win.

Reliability moats:

  Proven track record of autonomous operation without breaking things
  Sophisticated error detection and recovery
  Appropriate human-in-the-loop triggers
  Audit trails and explainability for regulated industries


This takes time to build. A startup with great AI but no production reliability can’t compete with an established player that’s proven they won’t accidentally delete your customer database.

Product Roadmap Priorities for IT 3.0

If you’re building B2B software in this transition, here’s what to prioritize:

Immediate (Next 6 months)


  Audit your product for agent-friendliness
    
      Can AI agents use your product via API without human clicking?
      Do your APIs expose reasoning context or just CRUD?
      Is your value delivery outcome-based or tool-based?
    
  
  Define outcome metrics
    
      What measurable business outcomes does your product deliver?
      How do you prove ROI to customers?
      Can you shift pricing from seats to outcomes?
    
  
  Identify the “glue work” your product creates
    
      What manual tasks do users do because your product doesn’t?
      What coordination happens outside your product?
      These are your vulnerabilities—automate them before competitors do
    
  


Medium-term (6-18 months)


  Build agent-native APIs
    
      Context-rich endpoints that expose reasoning inputs
      Outcome-oriented operations
      Reasoning transparency and explainability
    
  
  Invest in domain-specific models
    
      Build proprietary training datasets
      Fine-tune models for your specific use case
      Own the model layer, don’t just wrap GPT
    
  
  Redesign for autonomous operation
    
      Move from “tool that helps users” to “system that delivers outcomes”
      Build reliability, error handling, and guardrails for autonomous work
      Create appropriate escalation paths
    
  


Long-term (18-36 months)


  Build context accumulation
    
      Capture decision rationale, not just transactions
      Build knowledge graphs of entity relationships
      Make your product smarter the longer customers use it
    
  
  Distribution strategy
    
      Embed in existing workflows
      Build ecosystem/platform plays
      Own vertical-specific go-to-market
    
  
  Shift business model
    
      Outcome-based pricing
      Service as Software delivery
      Value-based contracts
    
  


Conclusion: Build for Agents, Not Users

The shift from IT 2.0 to IT 3.0 rewrites product strategy fundamentals. The principles that made SaaS successful—great UX, integrations, feature completeness—are necessary but no longer sufficient.

Winning products in IT 3.0:

  Design for AI agents as primary consumers, humans as exception handlers
  Deliver outcomes, not tools
  Build reasoning context and domain-specific models
  Measure success by business impact, not feature adoption
  Create defensibility through context accumulation and outcome delivery


Losing products in IT 3.0:

  Optimize click-ops interfaces while competitors build agent-native systems
  Add “AI features” without redesigning for autonomous operation
  Rely on switching costs and training lock-in that AI eliminates
  Compete on features when the market values outcomes


The uncomfortable truth: most SaaS products are building for a paradigm that’s already dying. Human-centric interfaces made sense when humans operated the software. When agents operate autonomously, your beautiful UI is irrelevant.

Your strategic decision: Are you building the next generation of infrastructure, or optimizing the last generation’s paradigm?

The window is narrow. Enterprises are making IT 3.0 buying decisions now. Products that win these early deployments will accumulate context and prove reliability while competitors are still adding AI chatbots to IT 2.0 interfaces.

Choose fast. Your competitors already are.



Related Reading:

  AI Transformation: Navigating the End of IT 2.0 Bullshit Jobs - Organizational implications of the IT 3.0 shift
  Service as Software - Delivering outcomes through AI agents
  AI Impact on Software - Cost to Implement, Time to Implement, and Time to Utility


References

Articles &amp;amp; Blog Posts

  Service as Software - On delivering outcomes through AI agents
  AI Impact on Software - Cost to Implement, Time to Implement, and Time to Utility
  AI Transformation: Navigating the End of IT 2.0 Bullshit Jobs - Organizational transformation framework


@article{
    leehanchung_building_ai_native,
    author = {Lee, Hanchung},
    title = {Building for IT 3.0: Why Agent-Native Products Will Crush SaaS Click-Ops},
    year = {2025},
    month = {10},
    day = {18},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/10/18/building-ai-native-products/}
}



        </description>

        <pubDate>Sat, 18 Oct 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/10/18/building-ai-native-products/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/10/18/building-ai-native-products/</guid>

      </item>

    

      <item>

        <title>Enterprise AI Transformation: The 4-Set Framework for IT 3.0</title>

        <description>

          From SaaS glue work to AI-native systems: mindset, toolset, skillset, data - 

          If you’re leading enterprise transformation, you need to understand what’s happening to your workforce, your tech stack, and your competitors right now. Three waves of enterprise IT have systematically created and destroyed entire job categories. The third wave is accelerating, and it will reshape your organization whether you’re ready or not.


  IT 1.0 built careers around custom in-house software. IT departments were strategic assets staffed with programmers and system operators who created bespoke systems mapping human processes to software workflows.
  IT 2.0 outsourced that intelligence to hundreds of SaaS solutions, creating a fragmented modular stack that spawned an entire ecosystem of “glue work” roles – SaaS admins, implementation engineers, system integrators, data analysts reconciling mismatched systems. Anthropologist David Graeber called these “bullshit jobs”: work that exists mainly because systems can’t talk to each other.
  IT 3.0 is dissolving this glue layer with AI-native systems and agents that can draft, coordinate, and produce outcomes without predefined workflows. The bullshit jobs of IT 2.0 are first on the chopping block. Just as CAD tools erased armies of draftsmen, or UBS’s trading automations emptied an entire stadium of trading floor in Stamford in 2012, today’s AI agents are already hollowing out sales ops, recruiting coordinators, junior devs, and SaaS admins.


Your strategic decisions today determine whether you’re building the next wave of infrastructure or clinging to a dying paradigm. Here’s what you need to know.

From IT 1.0 to IT 3.0

IT 1.0 – Building and Owning the Stack

Before the Internet and cloud computing, enterprises staffed full IT departments and procured from software vendors like IBM, Oracle, and SAP to build and run in-house systems. These were expensive and specialized, but tightly integrated with the business. This software translated existing human processes into workflows defined in bespoke software living on top of a database.

IT departments were core strategic assets, staffed with programmers, system operators, and managers who created bespoke systems. Jobs in this era were directly tied to creating or maintaining foundational business infrastructure. This period gave us foundational software development methodologies like Agile, born from Chrysler’s internal payroll system project. The work was complex and expensive, but it was essential.

IT 2.0 – SaaS and the Bullshit Job Boom

The 2000s marked the era of SaaS, with software now delivered over the Internet. Companies shifted from building internally to subscribing. This democratized access to powerful tools like cloud-based CRMs, HR suites, and ERPs. This enabled consumption-based pricing and product-led growth business models. At the same time, this created a new systemic problem: a fragmented modular stack of hundreds of applications that are siloed. Data silos and operational silos emerged everywhere, with Excel files pushed via email to glue everything together.

This fragmentation gave birth to a massive ecosystem of what David Graeber termed “bullshit jobs” – roles that exist primarily to service the friction between systems. These aren’t jobs that create direct value; they exist to pay the “information organization tax.” Duct tapers patch half-working systems together with shoddy code or send Excel files via email. Box tickers send Excel sheets to half a dozen folks for check-off, creating the appearance that something productive is being done when it is not.

This boom in “glue work” included:

  System Administrators: Entire careers built around configuring, managing, and patching platforms like Salesforce or Workday.
  System Integrators: Specialists whose job was to connect one SaaS tool to another and migrate data between them.
  Data Entry &amp;amp; Junior Analysts: Armies of people hired to manually move data from spreadsheets and PDFs into rigid SaaS formats.
  Operations Roles (Sales Ops, HR Ops, Rev Ops): Professionals who spend their days coordinating approvals, managing handoffs, and bridging gaps that APIs and integrations never quite solved.


Graeber’s notion of bullshit jobs became reality: people spending careers moving data from one rectangle on their monitor to another. This wasn’t work in the economic sense of creating value – it was a side effect of SaaS modularity and weak interoperability.

The information organization tax was massive. Meetings, cross-departmental handoffs, redundant reporting – all to coordinate intelligence scattered across silos.

This era also saw the rise of IT Consulting and Outsourcing, with inherent misaligned incentives. Shoddy software was developed to maximize overall contract value, including system integration, data migration, and ongoing maintenance contract renewals. The now-hollowed-out IT departments were no longer technical enough to ensure quality of work. Boeing’s outsourcing of its software design through layers of sub-contracting was the biggest showcase of this failure.

This entire industry — admins, ops, consultants — was built on a foundation of systems that couldn’t talk to each other. AI is now removing that foundation.

IT 3.0 – AI Dissolves the Glue

The opportunity: Organizations that transforms first will operate with half the headcount and twice the velocity of their competitors.

The AI-native wave is fundamentally different. Instead of creating more silos, AI agents and copilots are dissolving the “glue” that holds the fragmented IT 2.0 stack together. These systems can draft workflows, translate data between formats, and execute complex processes across multiple tools without human intervention or mediation.

Just as CAD tools turned hundreds of draftsmen into a handful of designers with software, or as UBS shuttered its massive Stamford, Connecticut trading floor in 2012 after algorithmic trading made human traders redundant, AI is now dismantling the SaaS-created glue layer. All the workflow definitions and playbooks are becoming obsolete and will be codified within the model and tools.




  IT 3.0 software now requires environments and infrastructure for agents to operate, instead of translating human-centric processes from the 1900s into workflows – the era of agent-computer interfaces.


These roles are being eliminated now, not in some distant future:


  SaaS admins → AI copilots auto-generate workflows, reports, and integrations. If you’re still hiring Salesforce admins, you’re building the wrong team.
  Recruiting coordinators → Chatbots already schedule interviews and screen resumes. This role has maybe 18 months left.
  Entry-level developers → Code assistants handle glue code and CRUD apps. Your hiring funnel should reflect this.
  Sales ops &amp;amp; BDRs → AI personalizes outreach and processes leads at volume. Manual outreach doesn’t scale anymore.
  Finance &amp;amp; HR ops → AI reconciles invoices, updates HR records, and generates compliance docs. Every manual handoff is a liability.


The changes ars accelerating because the barriers to adoption are collapsing. IT 1.0 required massive capital expenditure and months of implementation. IT 2.0 reduced cost but still required lengthy training and change management. IT 3.0 collapses time to value: it takes seconds to issue commands to ChatGPT instead of months training super users on PeopleSoft for adminstrators or EPIC for healthcare. When adoption barriers disappear, displacement accelerates.

Which IT 2.0 Tools Are Most at Risk?

AI will hit hardest where SaaS tools created clerical overhead:


  CRM (Salesforce, HubSpot) – lead enrichment, pipeline updates, report generation: all ripe for AI automation.
  ATS &amp;amp; HR platforms (Workday, Greenhouse) – resume parsing, candidate scheduling, payroll entry: trivial for AI.
  Customer support platforms (Zendesk, ServiceNow) – tier-1 support is already being offloaded to LLM agents.
  Project management (Asana, Jira, Monday) – task creation, updates, and cross-tool syncing will be handled by AI copilots, reducing the need for ops roles.
  Finance/ERP (NetSuite, SAP) – invoice matching, expense categorization, forecasting: automatable.


The SaaS platforms may survive, but the job ecosystems around them will not.

AI Transformations

One of the biggest challenges in AI transformation is how we measure. Poorly defined metrics will incentivize the wrong behavior.

Take engineering as an example:

  Lines of code written by AI
  Weekly active users on Cursor
  Percentage of PRs reviewed by AI


These metrics measures only adoption and utilization of AI tools and incentivize activity. These measures activities and outputs, not impacts and outcomes. People are equipped with AI tools but operates the same way as they did before ChatGPT. Cargo-cult AI transformation.

What actually matters and should be tracked are:

  Product lead time: from PM idea inception to production.
  Ticket resolution time: Time to resolve request and support tickets
  Change fail percentage: how often do your deployments blow up and require hotfixes.


These aren’t novel ideas; they’re adapted from DORA metrics. But implementing them requires serious platform investment in analytics and observability.

And if we’re being realistic, most engineering hours aren’t spent building features. They’re occupied by operational overhead and enterprise architecture complexity, with interdependencies between services, org silos, and coordination tax. The microservices dream turned into a distributed monolith nightmare.



This problem isn’t unique to engineering. Every function needs to figure out what velocity means for them, and the answers are completely different. Legal measures contracts processed per month, not contracts reviewed—velocity over volume in progress. Marketing tracks campaign velocity, concept to launch—how fast can you test and iterate? Sales optimization is deal cycle time, qualified lead to close, not pipeline size. Finance cares about close cycle time and days to financial insights. Same transformation, completely different metrics.

This is why centralizing AI transformation is very hard to do well. Many companies are setting up Chief AI Officers and AI Enablement Engineering Teams to “manage” the IT 3.0 shift. This creates exactly the wrong dynamic. One overwhelmed function while everyone else waits for direction and navigates bureaucracy. You end up with coordination overhead on top of your existing coordination overhead.

For companies actually making the IT 3.0 transition work, every executive owns AI transformation for their domain. Legal, finance, marketing, sales, engineering. Each has dedicated teams and executive accountability to transform their function from within. This is a business transformation, not IT transformation. The Chief AI Officer org chart is IT 2.0 thinking applied to an IT 3.0 problem.

Measuring AI Transformation with 4-Sets Framework

To measure AI Transformation, we can leverage the 4-Sets Framework used in the early days of Big Data -  mindset, toolset, skillset, and datasets. This helps us to analyze,

Mindset: Getting Comfortable with Probabilistic Systems

AI is fundamentally different from deterministic software. Same input, different outputs. “Correct” is contextual, not binary. This breaks every QA process and approval workflow designed for deterministic systems. When AI works 95% of the time, quality control becomes exponentially harder. Most organizations can’t get comfortable with “95% accurate” instead of “100% correct.”

The hardest concept for product teams to grasp is that AI makes shiny demos trivially easy and production deployment brutally hard. Compelling demos get built in days. Production at scale—with acceptable error rates, latency, and cost—takes months. The gap between “it works in the demo” and “it works at 10 requests per second” is where most AI projects die. OpenAI built their Agent Builder in 6 weeks with primitive user experiences. This made investors realize that n8n is the actual category leader and allows it to raised $180m at $2.5B valuation.

This requires a cultural shift most companies aren’t prepared for. You have to allow failures to happen. Innovation requires experimentation. Experimentation requires accepting failure. If your culture punishes failed AI experiments the same way it punishes product failures or production outages, nobody will innovate. There will be theater. Cargo-cult AI adoption where engineers use Cursor exactly like they use VSCode. Weekly active users up, changes in productivity flat. All form, no function. The shift from “zero defects” culture to “fast iteration” culture is the hardest change to make, and it’s the one that determines whether transformation succeeds or becomes expensive theater.

Toolset: Give Your People AI, Then Get Out of the Way

The toolset question isn’t “what should we build?” It’s “what do we give employees so they can experiment?” Enable the early adopters. Most organizations approach this backwards. They lock down AI access while forming committees to “evaluate use cases.” By the time the committee finishes, your competitors have six months of experimentation learning ahead of you. GPT 3.5 has now became GPT 4, and you just wasted one full generation of AI progress. Just look at the adoption curve and its not hard to realize that one day in AI is seven days in software. The progress of AI research and engineering is the hyperbolic time chamber in Dragon Ball.



Start with access. Enterprise licenses for Claude, ChatGPT, coding assistants like Cursor or Windsurf. The ROI comes from letting your team discover what works instead of trying to predict it from a conference room.

Remove approval bureaucracy for internal tools. If an engineer wants to try auto-generating test cases, or marketing wants to experiment with campaign copy variations, they shouldn’t need VP sign-off. Create guardrails — what data can’t leave the organization, what decisions need human review — then let teams iterate within those boundaries. The organizations winning at this have clear rules and fast iteration, not slow approvals and perfect safety.

The infrastructure and platform shift is fundamental. You need environments where AI can actually operate — API access to internal systems, data pipelines that AI can query, workflows that can be triggered programmatically. If your systems only work through web UIs and manual clicking, AI can’t help much. This doesn’t mean rebuilding everything overnight, but it does mean every new system should be designed for programmatic access first, human interfaces second. Build for agent compute interfaces.

Build safe sandboxes. The real blocker for AI is that nobody can access production data to try things. Create environments with representative data where people can experiment without going through 20 levels of approval or risking compliance violations. Sanitized customer data, recent transaction samples, realistic test cases. Make it real enough to be useful, safe enough to be accessible.

Skillset: Developing AI Literacy in Your Existing Workforce

The skillset question is challenging. It’s not who should we hire, but how do we develop our existing people. There’s institutional knowledge and domain expertise sitting in the current workforce. Replacing them with AI-native hires means throwing away context that took years to build.

Start with AI literacy training, but make it practical. Nobody needs another 30 minutes video on “what is generative AI.” They need hands-on practice using AI tools for their actual work. Give your legal team access to contract analysis tools and let them discover what works. Let your sales ops team experiment with lead scoring. Let engineers try code generation on real tickets. Learning happens through doing, not watching presentations.

The ratio of architects to operators is inverting. You need more people who can define what should be automated and govern how it operates, fewer people executing repetitive workflows. Some of your SaaS admins can become platform engineers if you invest in their development. Some of your operations coordinators can become exception handlers and strategic decision-makers. But this requires intentional churning and upskilling, not just telling people to “learn AI.”

DevOps taught us the “shift left” movement—pushing responsibility to development teams instead of operations teams. IT 3.0 accelerates this dramatically. You need platform engineers building infrastructure that enables AI to operate, not program managers coordinators managing handoffs between silos.



Retrain when someone has deep domain knowledge but outdated execution skills. For example, retrain that SaaS admin who knows every corner case in your business rules. Restructure when the role itself is pure coordination overhead with no domain expertise, e.g., data entry coordinators, implementation engineers patching systems together, junior developers writing glue code.

The roles that survive require one of three things: deep technical expertise (machine learning engineers, platform engineers, infrastructure architects), deep context and judgment (exception handlers, strategic decision-makers), or genuine human connection (relationship building, complex negotiation, empathy-driven work). Everything else is getting automated, and your workforce development strategy needs to account for this reality instead of pretending you can train your way around it.

Dataset: The Enterprise Data Reality Nobody Wants to Talk About

Here’s the uncomfortable truth: most enterprises have tons of data and almost none of it is useable for AI reasoning. SaaS work in silos - Salesforce can be run without talking to Workday with humans bridging the gaps. AI can’t. Reasoning engines need comprehensive cross-functional context to make decisions, and your data is scattered across dozens of systems with inconsistent schemas, undocumented business logic, and quality issues nobody has prioritized fixing because “it works fine for reporting.”

The gap between “we have the data” and “AI can reason with our data” is measured in quarters or years. You need historical decision rationale, not just transaction logs. Relationship graphs between entities, not just foreign keys. Temporal context showing why things changed over time. Cross-functional workflows documenting how sales, legal, and finance actually interact, not the idealized process in the wiki nobody updates.

This is unglamorous infrastructure work that doesn’t demo well but blocks everything else. Data quality becomes infrastructure, not a nice-to-have. Someone needs to own making datasets useable, not just available. Start with safe sandboxes where teams can experiment with representative production data without 20 levels of approval. Prove value with sanitized data, then earn access to more sensitive datasets through results, not presentations.

Build infrastructure that enables experimentation without exposure. Clear governance and guardrails on what data can’t leave the organization and what decisions need human review, then let teams move fast within those boundaries. Companies that solve this first will have compounding advantages as their AI systems get smarter from accumulated context while competitors are still filling out data access request forms.

The Strategic Warning: Don’t Create AI Slop Janitors

Rushing to implement AI without redesigning workflows creates worse jobs than the ones you’re eliminating.

While AI eliminates IT 2.0’s glue work, poorly implemented AI is creating its own category of bullshit jobs: AI Slop Janitors. This happens when organizations bolt AI onto existing processes instead of rebuilding from first principles.

Look at the content industry: writers who once led creative teams now edit ChatGPT’s robotic prose for 1-5 cents per word (versus 10+ cents for original writing). They fix the same formulaic mistakes daily – removing “delve” and “nevertheless,” fact-checking hallucinations, making text sound less awkward. The absurdity peaks when freelance platforms use AI detectors while simultaneously hiring people to make AI text undetectable. Human workers are being brought in to fix what AI gets wrong.

This pattern is emerging across industries:

  AI Tutors teaching LLMs to write better, e.g., xAI Presentation and Writing Tutor
  Customer service bots requiring constant human backup for edge cases
  Engineering teams spending more time fixing AI-generated code than writing it themselves, e.g., The era of AI Slop cleanup has begun
  “Autonomous” vehicles with remote human operators standing by, e.g., Waymo’s Fleet Response Team


These aren’t valuable human-in-the-loop systems. They are temporary workers thats used to clean up AI’s mess, and once AI learns these, these jobs will be replaced. Organizations creating these roles are wasting capital on the wrong side of the transition. If your AI implementation plan includes hiring “AI quality reviewers” or “AI content editors,” you’re implementing AI wrong.

Conclusion

The transition from IT 2.0 to IT 3.0 is messy and accelerating. The glue work jobs are disappearing whether you’re ready or not. But the replacements aren’t automatically better – poorly implemented AI creates worse bullshit jobs than the ones being eliminated. AI Slop Janitors stare into the abyss and fix the same robotic mistakes or the same vibe coded apps or workflows.

Organizations that move decisively will operate with half the headcount and twice the velocity of their competitors. Those that don’t will find themselves either:

  Carrying dead weight in IT 2.0 roles while competitors move faster
  Creating AI Slop Janitor positions because they bolted AI onto broken processes
  Disrupted entirely by AI-native competitors who rebuilt from first principles


The opportunity is real, but narrow. As AI eliminates the information organization tax, capital and talent can shift to work that genuinely requires human judgment, deep context, and strategic thinking. But this shift won’t happen organically – it requires deliberate choices about team structure, workflow redesign, and where to compete.

Your organizational priorities:

  Assess org maturity using the 4-Sets transformation framework: Mindset, Dataset, Toolset, Skillset
  Redesign workflows from first principles for AI, not bolting AI onto existing processes
  Distribute AI ownership across functions – every team owns their domain’s transformation
  Invest in platform engineering and AI infrastructure, not more ops coordinators
  Build safe sandbox environments for experimentation without approval bureaucracy


The bullshit jobs aren’t disappearing – they’re being replaced by different bullshit jobs. The question is whether you’re building the infrastructure that eliminates them, or whether you’re creating the next generation of make-work. Choose fast, because your competitors already are.

References

Articles &amp;amp; Blog Posts

  Bullshit Jobs - David Graeber’s original essay on bullshit jobs
  How the Boeing 737 Max Disaster Looks to a Software Developer - Analysis of Boeing’s software outsourcing failure
  Service as Software - On delivering outcomes through AI agents
  AI Impact on Software - Cost to Implement, Time to Implement, and Time to Utility
  Humans Hired to Fix AI Slop - NBC News on AI cleanup jobs
  The Era of AI Slop Cleanup Has Begun - Discussion on AI code cleanup
  Waymo’s Fleet Response Team - Human operators for self-driving cars


Job Postings

  xAI Presentation and Writing Tutor - Example of AI tutor positions


@article{
    leehanchung_bullshit_jobs,
    author = {Lee, Hanchung},
    title = {The End of &quot;Bullshit Jobs&quot;: From IT 1.0 to the AI-Powered 3.0 Era},
    year = {2025},
    month = {09},
    day = {19},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/09/19/bullshit-jobs/}
}



        </description>

        <pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/09/19/bullshit-jobs/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/09/19/bullshit-jobs/</guid>

      </item>

    

      <item>

        <title>Statistics for AI/ML, Part 4: pass@k and Unbiased Estimator</title>

        <description>

          Understanding common metrics in LLM benchmarks - 

          Every time AI labs release new models, we see an evaluation metric called $\text{pass@}k$, where $k$ can be any integer number such as $\text{pass@}1$. It might sound like passing a test at the $k$th attempt, but this metric is far more sophisticated and plays a crucial role in how we build reliable AI applications in production.

As an example, here’s OpenAI GPT-5’s performance on AIME.




  $\text{pass@k}$ does not mean the model passing a test in $k$ attempts. It is calculated using an estimator.


Most terms in AI, Machine Learning, and Reinforcement Learning have specific technical definitions that deviate from plain English, from accuracy and agents to recall and retrieval-augmented generation (RAG). Understanding $\text{pass@}k$ is particularly important because it directly influences how we evaluate models and design sampling strategies for compound AI systems.

This blog post aims to demystify what $\text{pass@}k$ means, explain the mathematics behind its calculation, and show you how to leverage this metric to build more reliable AI applications.

Due to the inherent randomness in model outputs, we don’t know the true $\text{pass@}k$ value. Instead, we must estimate it based on a finite number of experiments or samples. The formula we use to calculate $\text{pass@}k$ is called an estimator.

Unbiased Estimator

In statistics, an unbiased estimator is a method for estimating a population parameter like a mean or probability that gives the correct value on average. “Unbiased” means the estimator’s expected value equals the true value it’s trying to estimate across many samples.

For example, if you’re estimating the average height of people in a city $\mu$, an unbiased estimator, e.g., the sample mean, will, over many samples, average out to $\mu$. Mathematically, if $\theta$ is the parameter and $\hat{\theta}$ is the estimator, it’s unbiased if:

\[E(\hat{\theta}) = \theta\]

where $E(\hat{\theta})$ is the expected value of the estimator.

The Biased Estimator Problem

Lets call our emperical estimate of $\text{pass@}k$ as $\hat{p}$. The naive approach to calculate $\hat{p}$ can be done by running n trials and dividing the number of successful trials by n. For example, if 30 out of 100 code samples are correct, then $\hat{p} = 30/100 = 0.3$.

Using this empirical probability, we can try to estimate $\text{pass@}k$ with the formula:

\[\text{pass@}k = 1 - (1 - \hat{p})^k\]

However, this is a biased estimator. The term $(1 - \hat{p})^k$ represents the probability that all $k$ samples fail, calculated as the product of $k$ independent failure probabilities. This multiplication only works when each sample is independent, meaning we’re drawing with replacement from an infinite population or putting samples back after each draw.

But in reality, when we select k samples from our n generated samples, we’re sampling without replacement. Once we pick a sample, we do not put it back. This means:


  If our first sample is incorrect, we have one fewer incorrect sample in the pool
  The probability of selecting another incorrect sample changes from $(n-c)/n$ to $(n-c-1)/(n-1)$ for $\text{pass@}1$.
  The samples are no longer independent events


This mismatch between the formula’s assumption of independent draws with replacement and the reality dependent draws without replacement causes us to systematically underestimate the true $\text{pass@}k$.

OpenAI illustrated this issue in Chen et al, 2021.



U-Statistics and the Unbiased Estimator

OpenAI’s solution uses U-statistic (the letter ‘U’ stand for unbiased, not the shape) to create an unbiased estimator. U-statistics are a class of statistics that provide minimum-variance unbiased estimators for parameters that can be expressed as expected values of symmetric functions.

Their estimator is:

\[\text{pass@}k = 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}}\]

where:

  $n$ is the total number of samples
  $c$ is the number of correct samples
  $k$ is the number of samples we’re considering


This formula calculates the probability that all $k$ selected samples are incorrect (from the $n-c$ incorrect samples), then subtracts from 1 to get the probability that at least one is correct.

Special Case: When $k=1$

When $k=1$, the unbiased estimator simplifies beautifully:

\[\text{pass@}1 = 1 - \frac{\binom{n-c}{1}}{\binom{n}{1}} = 1 - \frac{n-c}{n} = \frac{c}{n}\]

Number of correct samples divided by total number of sampels is exactly $\hat{p}$, our empirical success rate. This makes intuitive sense: when we only select one sample, there’s no difference between sampling with or without replacement. We’re just picking one sample from our pool. The probability of success is simply the proportion of successful samples, which validates that our unbiased estimator reduces to the correct simple case.

Why This Estimator is Better


  Unbiased: The expected value of this estimator equals the true $\text{pass@}k = 1 - (1 - p)^k$
  Accounts for finite sampling: It correctly handles sampling without replacement from a finite set
  Minimum variance: Among all unbiased estimators, U-statistic provide the minimum-variance estimate


Practical Example

Suppose you generate $n=10$ code samples and $c=3$ are correct:

  Naive estimator: $\text{pass@}5 = 1 - (1 - 0.3)^5 = 1 - 0.7^5 = 0.83193$
  Unbiased estimator: $\text{pass@}5 = 1 - \frac{\binom{7}{5}}{\binom{10}{5}} = 1 - \frac{21}{252} = 0.91667$


The unbiased estimator gives a significantly higher probability (91.7%) compared to the naive estimator (83.2%). This difference occurs because the naive estimator treats each draw as independent, while the unbiased estimator correctly accounts for the fact that we’re selecting 5 samples from a finite pool of 10 without replacement. The difference becomes more pronounced with smaller sample sizes or when k approaches n.

Applications in AI Systems

Understanding $\text{pass@k}$ helps us interpret benchmarks and design better AI systems.

Evaluation and Benchmarking

When models are evaluated on benchmarks like HumanEval or MBPP:

  $\text{pass@}1 = 70\%$ means a single attempt has 70% chance of being correct
  $\text{pass@}10 = 90\%$ means at least one of 10 attempts will likely be correct
  This gap reveals the potential benefit of sampling multiple solutions


The metric extends beyond code to mathematical problem solving (AIME, GSM8K) and reasoning tasks, where verification is often programmatic.

Majority Voting Design Pattern

$\text{pass@k}$ insights can be applied as a design pattern in compound AI systems:


  Self-Consistency: Generate k responses and use majority voting
    def self_consistency(prompt, model, k=5):
  responses = [model.generate(prompt, temperature=0.7) for _ in range(k)]
  # Return most common answer or execute and verify for code/math
  return most_common(responses)
    
  
  
    Best-of-N: Generate $N$ candidates, score them with a verifier, return the best
  
  Temperature tuning: Higher temperature increases diversity (better $\text{pass@k}$ for $k&amp;gt;1$), while lower temperature improves $\text{pass@}1$


If $\text{pass@}10$ is significantly higher than $\text{pass@}1$, implementing multi-sampling could improve reliability though this trades off against latency and cost.

Conclusion

The $\text{pass@}k$ metric is a fundamental evaluation tool in LLM benchmarks, but its meaning is often misunderstood. Rather than simply counting how many attempts it takes to pass a test, $\text{pass@}k$ represents the probability that at least one of k independently sampled solutions is correct.

Key takeaways:

  $\text{pass@}1$ is not about “passing on the first try” but rather the probability of a single sample being correct
  Due to randomness in model outputs, we need estimators to calculate $\text{pass@}k$ from finite samples
  The naive estimator is biased and underestimates the true $\text{pass@}k$
  OpenAI’s unbiased estimator provides accurate estimates by properly accounting for sampling without replacement
  The gap between pass@1 and pass@k reveals opportunities for improving reliability through multi-sampling


Understanding these technical definitions helps us better interpret model performance claims and benchmark results. When you see “$\text{pass@}1 = 92\%$” in the next model release, you’ll know it means the model has a 92% probability of generating a correct solution in a single attempt, calculated using an unbiased statistical estimator.

References

  Introducing GPT-5
  Chen, M., Tworek, J., Jun, H., Yuan, Q., de Oliveira Pinto, H. P., Kaplan, J., Edwards, H., Burda, Y., Joseph, N., Brockman, G., Ray, A., Puri, R., Krueger, G., Petrov, M., Khlaaf, H., Sastry, G., Mishkin, P., Chan, B., Gray, S., … Sutskever, I. (2021). Evaluating large language models trained on code. arXiv preprint arXiv:2107.03374.
  DeepSeek-AI, Guo, D., Yang, D., Zhang, H., Song, J., Zhang, R., Xu, R., Zhu, Q., Ma, S., Wang, P., Bi, X., Zhang, X., Yu, X., Wu, Y., Wu, Z. F., Gou, Z., Shao, Z., Li, Z., Gao, Z., Liu, A., Xue, B., Wang, B., Wu, B., Feng, B., Lu, C., Zhao, C., Deng, C., Zhang, C., Ruan, C., Dai, D., Chen, D., Ji, D., Li, E., Lin, F., Dai, F., Luo, F., Hao, G., Chen, G., Li, G., Zhang, H., Bao, H., Xu, H., Wang, H., Ding, H., Xin, H., Gao, H., Qu, H., Li, H., Guo, J., Li, J., Wang, J., Chen, J., Yuan, J., Qiu, J., Li, J., Cai, J. L., Ni, J., Liang, J., Chen, J., Dong, K., Hu, K., Gao, K., Guan, K., Huang, K., Yu, K., Wang, L., Zhang, L., Zhao, L., Wang, L., Zhang, L., Zhang, L., Tang, M., Li, M., Tian, N., Huang, P., Zhang, P., Wang, Q., Chen, Q., Du, Q., Ge, R., Zhang, R., Pan, R., Wang, R., Chen, R. J., Jin, R. L., Chen, R., Lu, S., Zhou, S., Chen, S., Ye, S., Wang, S., Yu, S., Zhou, S., Pan, S., Li, S. S., Zhou, S., Wu, S., Ye, S., Yun, T., Pei, T., Sun, T., Wang, T., Zeng, W., Zhao, W., Liu, W., Liang, W., Gao, W., Yu, W., Zhang, W., Xiao, W. L., An, W., Liu, X., Wang, X., Chen, X., Nie, X., Cheng, X., Liu, X., Xie, X., Liu, X., Yang, X., Li, X., Su, X., Lin, X., Li, X. Q., Jin, X., Shen, X., Chen, X., Sun, X., Wang, X., Song, X., Zhou, X., Wang, X., Shan, X., Li, Y. K., Wang, Y. Q., Wei, Y. X., Zhang, Y., Xu, Y., Li, Y., Zhao, Y., Sun, Y., Wang, Y., Yu, Y., Zhang, Y., Shi, Y., Xiong, Y., He, Y., He, Y., Piao, Y., Wang, Y., Tan, Y., Ma, Y., Liu, Y., Guo, Y., Ou, Y., Wang, Y., Gong, Y., Zou, Y., He, Y., Xiong, Y., Luo, Y., You, Y., Liu, Y., Zhou, Y., Zhu, Y. X., Xu, Y., Huang, Y., Li, Y., Zheng, Y., Zhu, Y., Ma, Y., Tang, Y., Zha, Y., Yan, Y., Ren, Z. Z., Ren, Z., Sha, Z., Fu, Z., Xu, Z., Xie, Z., Zhang, Z., Hao, Z., Ma, Z., Yan, Z., Wu, Z., Gu, Z., Zhu, Z., Liu, Z., Li, Z., Xie, Z., Song, Z., Pan, Z., Huang, Z., Xu, Z., Zhang, Z., Zhang, Z. (2025). DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning. arXiv preprint arXiv:2501.12948. https://doi.org/10.48550/arXiv.2501.12948
  U-statistic
  Reasoning Series, Part 4: Reasoning with Compound AI Systems and Post-Training


@misc{lee2025passk,
    author = {Lee, Hanchung},
    title = {Statistics for AI/ML, Part 4: pass@k with Unbiased Estimator},
    year = {2025},
    month = {09},
    howpublished = {\url{https://leehanchung.github.io/blogs/2025/09/08/pass-at-k/}},
    url = {https://leehanchung.github.io/blogs/2025/09/08/pass-at-k/}
}



        </description>

        <pubDate>Mon, 08 Sep 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/09/08/pass-at-k/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/09/08/pass-at-k/</guid>

      </item>

    

      <item>

        <title>How AI Tools Are Reshaping Software Development Team Responsibilities</title>

        <description>

          A RACI Matrix Guide to Navigating the Blurred Lines Between PM, Engineering, and Design Roles - 

          The bottom line: Modern AI tools like ChatGPT, Claude, and Cursor are acting as powerful democratizers in software development. They are blurring traditional role boundaries, enabling product managers to draft code, engineers to mock up interfaces, and designers to prototype functionality. This newfound capability allows non-specialists to achieve surprisingly good results in unfamiliar domains. However, this democratization creates a crucial paradox: at the same time it lowers the barrier to entry, it also amplifies the need for true expertise. Only a skilled expert can properly verify, refine, and elevate an AI generated artifact to meet production level standards.

As these boundaries shift throughout the software development lifecycle (SDLC), teams are facing a fundamental questions: Who owns what when everyone can do everything? The solution lies not in abandoning structure, but in reinforcing it. Teams that clearly define who is Responsible, Accountable, Consulted, and Informed (RACI) will thrive. Those who allow ownership to become ambiguous will drown in decision paralysis. This framework provides a practical map for maintaining clarity and accountability in an AI-transformed landscape.

Understanding the RACI Framework

Before exploring how AI reshapes team dynamics, let’s first understand the roles within the RACI matrix. This is designed to eliminate confusion by assigning clear ownership. For any given task or deliverables, roles are defined as follows:

RACI Definitions:

  Responsible: Does the actual work
  Accountable: Makes final decisions and owns outcomes (only one per task)
  Consulted: Provides input before decisions are made
  Informed: Kept updated on progress and decisions




Cross-functional SDLC in an AI Powered World

The traditional SDLC was built on specialization, with product managers, engineers, and designers operating in clearly defined lanes. With AI tools, those lanes have become more like suggestions. A designer might use an AI to generate front-end code for a prototype, or a PM might use a tool to write initial API documentation. The following RACI chart reflects a model for how to manage these newly fluid responsibilities without sacrificing accountability.


  
    
      SDLC Phase
      Responsible
      Accountable
      Consulted
      Informed
    
  
  
    
      Requirements &amp;amp; Discovery
      PM (business reqs)Designer (user research)
      PM
      SWE (feasibility)MLE (data needs)
      All stakeholders
    
    
      Planning &amp;amp; Design
      SWE (tech planning)MLE (ML design)Designer (UX design)
      PM (scope/timeline)
      Cross-functional teams
      Leadership
    
    
      Architecture &amp;amp; Technical Design
      SWE (system arch)MLE (model arch)
      SWE (system)MLE (ML components)
      Designer (constraints)PM (requirements)
      PM (progress)
    
    
      Implementation
      SWE (features)MLE (models)Designer (UI)
      SWE/MLE/Designer(respective domains)
      Cross-team dependencies
      PM (sprint updates)
    
    
      Testing &amp;amp; QA
      SWE (unit tests)MLE (model validation)Designer (usability)
      SWE (system quality)
      PM (acceptance criteria)
      Leadership
    
    
      Deployment
      SWE (app deploy)MLE (model deploy)
      PM (go/no-go decision)
      All teams(deployment readiness)
      Stakeholders
    
    
      Monitoring &amp;amp; Maintenance
      SWE (system health)MLE (model drift)
      SWE (uptime)MLE (model performance)
      Designer (UX issues)PM (metrics)
      LeadershipCustomers
    
  


Key Principles for AI-Transformed Teams

To navigate a world where AI tools enable everyone to contribute across disciplines, teams should anchor themselves with a few core principles.

1. Domain Expertise Still Rules

Think of AI as a co-pilot, not an autopilot. While anyone can generate a first draft, domain experts remain accountable for quality and execution. The software engineer is ultimately accountable for the system’s architecture and performance, the designer for the final user experience, and the product manager for the business outcomes and delivery commitments. AI empowers contributors, but it doesn’t replace the final judgment of a seasoned professional.

2. Clear Escalation Paths

When the person Responsible for a task hits a roadblock or consensus can’t be reached, there must be a clear path to the single individual who is Accountable. This person’s job is to make the final call, breaking ties and preventing critical decisions from languishing in committee. This clarity is essential for maintaining momentum.

3. Consultation Front-Loading

The most effective collaboration happens early. Heavily involve Consulted parties during the Requirements and Planning phases to surface constraints, dependencies, and new ideas when the cost of change is low. Once you move into the Architecture and Implementation phases, consultation should become more focused to avoid “thrashing” and analysis paralysis.

4. Information Flow Management

Keeping stakeholders Informed is about delivering signal, not noise. Use structured communication channels like status reports, dashboards, and sprint demos to update people without overwhelming them or creating an expectation that their input is required. This respects everyone’s time and focus while ensuring alignment.

Common Anti-Patterns to Avoid

As teams adapt to AI-assisted workflows, certain organizational anti-patterns become even more destructive. Be vigilant and steer clear of these common traps:

❌ Multiple Accountables: Assigning more than one “A” to a single task is a recipe for gridlock. When two people are in charge, no one is. This inevitably leads to decision paralysis and conflict.

❌ Accountability Gaps: If a phase or critical task has no one in the “A” column, it creates a vacuum of ownership. When things go wrong, this leads to blame-shifting and finger-pointing rather than problem-solving.

❌ Responsibility Without Accountability: Having someone “R”esponsible for work without a corresponding “A”ccountable owner is equally dangerous. It sets the doer adrift without a clear escalation path or final decision-maker.

❌ Over-Consultation: Packing the “C” column, especially during execution phases, grinds progress to a halt. While input is valuable early on, requiring too much consensus later in the process leads to endless debate and analysis paralysis.

Conclusion

AI tools are fundamentally reshaping the “how” of software development, but they don’t change the “who.” The democratization of technical skills is not a threat to experts but an opportunity to amplify their impact. By embracing a well-defined RACI framework, teams can harness the collaborative power of AI without sacrificing the clear lines of ownership required to build and ship great products. The teams that succeed won’t be those that let roles dissolve into chaos, but those that reinforce accountability with intention and clarity.

References

  Responsibility assignment matrix


@article{
    leehanchung_ai_sdlc_2025,
    author = {Lee, Hanchung},
    title = {How AI Tools Are Reshaping Software Development Team Responsibilities},
    year = {2025},
    month = {09},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/09/05/ai-transformation-sdlc/}
}



        </description>

        <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/09/05/ai-transformation-sdlc/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/09/05/ai-transformation-sdlc/</guid>

      </item>

    

      <item>

        <title>Software Engineering for Data Scientists, Part 1: Pydantic Is All You Need for Poor Performance Spaghetti Code</title>

        <description>

          Save planet earth, stop using Pydantic everywhere - 

          I love Pydantic. And I’ve witnessed some of the worst code written with Pydantic. Pure spaghetti, non-performant code.

There are two major anti-patterns in abusing Pydantic for maximum spaghetti. First anti-pattern is serdes debt. Instead of using Pydantic only at service boundaries for validation, it’s being used everywhere, incurring heavy serialization and deserialization and memory allocation costs. Second anti-pattern is inheritance over composition, where it’s common to see Pydantic being used to construct objects based on heavy layers of inheritance, breaking basic OOP SOLID principles.

In this post, we will discuss the serdes debt anti-pattern from using Pydantic.

Anti-pattern: SerDes Debt

Pydantic is primarily used for data validation, with support for data schema and data serialization and deserialization (serdes).

Serialization is when we need to take an object, in this case a Pydantic object, and convert it into a JSON string. Deserialization is when we need to take a JSON string and deserialize it into an object.

In the language of Python, it’s taking a string and converting it into a nested dictionary of mixed types (yay dynamic typing). Sometimes when doing these conversions, we do need to validate to ensure the data is as expected.

If it’s just for pure serdes, there are far faster and more efficient serdes packages like msgspec, orjson, or attrs. In fact, Pydantic can be set up to use orjson.


  The core usecase for Pydantic is for data validation. Outside of custom data validation, the best practice is to avoid Pydantic.


Here’s a simple benchmark to demonstrate why.

Performance Benchmark
We based our benchmark on a simple two-class data structure. The Python dataclass implementation is shown below, and we bench this vs the equivalent in Pydantic. We did not implement data validation in either.

@dataclass
class Address:
    street: str
    city: str
    country: str
    postal_code: str

@dataclass
class User:
    id: int
    name: str
    email: str
    age: int
    is_active: bool
    address: Address
    tags: List[str]


Based on this simple data model, we observe that Python dataclasses run far superior in both time and space complexity.






  Creation Performance:
    
      Dataclasses are ~6.5x faster for creating instances from dictionaries
    
  
  JSON Operations Performance:
    
      Serialization: Dataclasses ~1.5x faster
      Deserialization: Dataclasses ~1.5x faster
      Full round-trip: Dataclasses ~1.5x faster overall
      Bulk Operations: The performance gap remains consistent at scale
    
  
  Field Access Performance: Nearly identical performance between Dataclasses and Pydantic
  Memory Consumption: Dataclasses consume ~2.5x less memory


Tips on Fixing Pydantic Anti-patterns


  
    Only use Pydantic at service boundaries, e.g., API request and response validation. Do not use Pydantic within a service itself.
  


Here’s the Pydantic team themselves:



  
    Static type checking with mypy. Avoid dynamic type checking. If dynamic type-checking is really needed, rewrite in Rust.
  



  
    Composition over Inheritance. Object inheritance creates additional layers of abstraction. Duplication is far cheaper than having more abstraction. Don’t Repeat Yourself (DRY) should be used sparingly.
  




References

  JSON extra uses orjson instead of ujson #599
  Reddit: Should I use pydantic for all my classes?
  X: Developer priorities throughout their career - LeaVerou


Appendix

Time complexity
Performance Comparison: Pydantic vs Dataclasses
============================================================
Test data structure: Nested user profile with address
Iterations per test: 10,000
Python dataclasses: Built-in
Pydantic version: 2.5.3

Warming up...

Running benchmarks...

Benchmarking: Instance Creation from Dict

Instance Creation from Dict
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 0.0543 ms            0.0084 ms           
Median               0.0531 ms            0.0082 ms           
Min                  0.0497 ms            0.0076 ms           
Max                  0.0892 ms            0.0156 ms           
Stdev                0.0041 ms            0.0008 ms           

Dataclasses is 6.46x faster

Benchmarking: Convert to Dictionary

Convert to Dictionary
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 0.0287 ms            0.0153 ms           
Median               0.0282 ms            0.0151 ms           
Min                  0.0265 ms            0.0142 ms           
Max                  0.0421 ms            0.0234 ms           
Stdev                0.0023 ms            0.0011 ms           

Dataclasses is 1.88x faster

Benchmarking: Serialize to JSON String

Serialize to JSON String
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 0.0361 ms            0.0247 ms           
Median               0.0355 ms            0.0243 ms           
Min                  0.0334 ms            0.0228 ms           
Max                  0.0512 ms            0.0387 ms           
Stdev                0.0029 ms            0.0019 ms           

Dataclasses is 1.46x faster

Benchmarking: Deserialize from JSON String

Deserialize from JSON String
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 0.0678 ms            0.0463 ms           
Median               0.0669 ms            0.0457 ms           
Min                  0.0632 ms            0.0431 ms           
Max                  0.0943 ms            0.0612 ms           
Stdev                0.0048 ms            0.0027 ms           

Dataclasses is 1.46x faster

Benchmarking: Field Access

Field Access
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 0.0013 ms            0.0012 ms           
Median               0.0013 ms            0.0011 ms           
Min                  0.0011 ms            0.0010 ms           
Max                  0.0019 ms            0.0018 ms           
Stdev                0.0001 ms            0.0001 ms           

Dataclasses is 1.08x faster

Benchmarking: Bulk Creation (100 items)

Bulk Creation (100 items)
============================================================
Metric               Pydantic             Dataclasses         
------------------------------------------------------------
Mean                 54.312 ms            8.427 ms            
Median               53.867 ms            8.356 ms            
Min                  52.145 ms            8.123 ms            
Max                  58.923 ms            9.234 ms            
Stdev                1.234 ms            0.187 ms            

Dataclasses is 6.45x faster

SUMMARY
============================================================

Performance Summary:
- Instance Creation from Dict: Dataclasses is 6.46x faster
- Convert to Dictionary: Dataclasses is 1.88x faster
- Serialize to JSON String: Dataclasses is 1.46x faster
- Deserialize from JSON String: Dataclasses is 1.46x faster
- Field Access: Dataclasses is 1.08x faster
- Bulk Creation (100 items): Dataclasses is 6.45x faster

DETAILED JSON OPERATIONS COMPARISON
============================================================

Round-trip JSON test (dict -&amp;gt; object -&amp;gt; JSON -&amp;gt; object -&amp;gt; dict):
  Pydantic: 10.42 ms
  Dataclasses: 7.15 ms
  Ratio: 0.69x

Tested with Pydantic v2.5.3


Space Complexity
MEMORY USAGE COMPARISON
============================================================

1. Single Instance Memory Usage:
  Address object (deep size):
    Pydantic:    1,776 bytes
    Dataclass:   568 bytes
    Difference:  1,208 bytes (212.7% more)

  User object (deep size):
    Pydantic:    3,424 bytes
    Dataclass:   1,312 bytes
    Difference:  2,112 bytes (161.0% more)

2. Bulk Creation Memory Usage (1000 instances):
  Pydantic:    3,287.45 KB (3,287.45 bytes per instance)
  Dataclasses: 1,245.78 KB (1,245.78 bytes per instance)
  Difference:  2,041.67 KB (163.9% more)

3. JSON Operations Memory Usage:
  Per JSON deserialization:
    Pydantic:    4,256 bytes
    Dataclasses: 2,184 bytes
    Difference:  2,072 bytes

4. Attribute Storage Analysis:
  Pydantic User attributes:   8 stored attributes
  Dataclass User attributes:  7 stored attributes

  Pydantic internals:
    __dict__: 296 bytes (dict)
    __pydantic_fields_set__: 216 bytes (set)
    __pydantic_extra__: 0 bytes (NoneType)
    __pydantic_private__: 0 bytes (NoneType)
    address: 72 bytes (PydanticAddress)
    age: 28 bytes (int)
    email: 74 bytes (str)
    id: 28 bytes (int)
    is_active: 28 bytes (bool)
    name: 57 bytes (str)
    tags: 88 bytes (list)

  Dataclass internals:
    address: 72 bytes (DataclassAddress)
    age: 28 bytes (int)
    email: 74 bytes (str)
    id: 28 bytes (int)
    is_active: 28 bytes (bool)
    name: 57 bytes (str)
    tags: 88 bytes (list)

5. Memory Efficiency Summary:
  - Dataclasses use ~40-50% less memory per instance
  - Pydantic stores additional metadata for validation
  - The memory gap increases with more complex models
  - Consider memory usage for large-scale applications

6. Visual Memory Comparison (per 1000 instances):
  Pydantic:    [████████████████████████████████████████] 3,287.5 KB
  Dataclasses: [███████████████                         ] 1,245.8 KB


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {No Code, Low Code, Full Code},
    year = {2025},
    month = {07},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/06/26/no-code-low-code-full-code/}
}



        </description>

        <pubDate>Thu, 03 Jul 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/07/03/pydantic-is-all-you-need-for-performance-spaghetti/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/07/03/pydantic-is-all-you-need-for-performance-spaghetti/</guid>

      </item>

    

      <item>

        <title>No Code, Low Code, Real Code</title>

        <description>

          Skating to where the puck is going to be, not where it was - 

          Agent frameworks and workflow builders exist because the LLM model itself is not yet strong enough to be autonomous in completing the tasks.

It can be now. For those who are technical enough to do post training. And these post trained LLMs will render most agent frameworks and workflow tools obsolete.

Workflow Builders

We human users find comfort in using no code or low code tools to draw boxes on a blank canvas to create workflows and schematics. It feels great. It feels in control. But history has repeatly shown that this style of working quickly becoming obsoltele as technology progresses.

Let’s use a very recent example of this phenomenon as a case study.

Before the release of ChatGPT in late 2022, Gen AI was dominated by image generation models. It’s very common for hackers and builders to stitch together a bunch of image generation models in a workflow to generate the desired effects. Stable diffusion, image upscaling, LoRA, ControlNet, etc. The workflows are very customized and complex. And from here, ComfyUI was born in January 2023.

ComfyUI is an very advanced generative AI workflow tool that enables users to stitch together complex workflows involving many models, prompts, parameters, customizations to achieve the intended effect. Below is one of the image I find on Google. And this is not even a complex workflow. From this. many indie hackers built their buisiness on top of this.


The situation changed rapidly after ChatGPT launched its image generation capability in March of 2025. In mere two short years, model capabilities drastically improved. Creaters no longer need to stitch together complex workflows to achieve the same effects vs using ComfyUI.

We can now use one single prompt to edit images. Hell, we can even now generate a full short video using one single text prompt.

The model is the product.



Robotic Process Automation is not Agentic

Currently there are more than a few tools that masquerades and rebrands robotic process automation (RPA) workflow builders as ‘agentic’. There’s nothing agentic about these tools. Nada. Nil. Zip.

Don’t get me wrong. They are awesome tools for software engineering consultants to quickly stitch together some solution and sell to some non-technical customers to automate some tasks. More often than not, going from 0 to 1 captures majority of the value and there’s not enough value to scale from 1 to 100. The tiny white elephant that is burdensome to maintain but has enough value to be hanging onto.

Langflow, make, n8n are the perfect tools here, but they are absolutely not agentic.





Reinforcement Learning Agents

On the other hand, we are already witnessing full agentic behaviors in apps like ChatGPT, Claude Desktop, and Gemini App. Though they cannot do a very good job yet for some longer running tasks, they are absolutely amazing with their agentic capabiltiies including reasoning.

And some of us can achieve the same in narrower domains. Today. With proper system optimization and post-training. Without drawing boxes on canvas.

Conclusion

As AI/ML practioners, we can either choose to fight the last war or skate to where the puck is going. My preference is for the latter.

@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {No Code, Low Code, Full Code},
    year = {2025},
    month = {06},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/06/26/no-code-low-code-full-code/}
}



        </description>

        <pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/06/26/no-code-low-code-full-code/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/06/26/no-code-low-code-full-code/</guid>

      </item>

    

      <item>

        <title>MCP is not REST API</title>

        <description>

          Failing agent computer interaction design by wrapping MCP on top of REST API - 

          Model Context Protocol (MCP) is a prominent technology in 2025, generating buzz comparable to ChatGPT in 2023 and RAG in 2024. However, many common implementations simply create an MCP wrapper over existing API services.

This is a suboptimal design choice. This blog post will outline the design principles of RESTful APIs, the origins of MCP, Remote Procedure Calls (RPC), and explain why combining these two distinct design philosophies is detrimental to Agent-Computer Interfaces (ACI).

The Essence of API Design

Let’s define the goals of good API design. A well-designed API should be:


  Easy to understand. Other developers (including future you) can grasp each endpoint’s purpose at a glance.
  Consistent. It follows a clear set of conventions, so the mental overhead to learn it stays low.
  Extensible. Versioning and new features can be added without breaking API consumers.
  Efficient. It makes sensible use of network and compute resources.


While “Premature optimization is the root of all evil” is a common adage in Computer Science, it doesn’t fully apply to API design. An API is a contract, and changes become very difficult after implementation. Even with versioning, driving adoption of new versions requires significant effort. This is analogous to data schema design.

REST API Primer

REST API is a popular API style focused on resources and the actions performed on them. It uses HTTP as the transport layer and typically serializes data in JSON format.

Here’s a simple example REST API of a blog service:


  Retrieve all blogs
    GET /api/blogs
    
  
  Retrieve a specific post
    GET /api/blogs/{id}  
    
  
  Create a blog post
    POST /api/blogs  
Content-Type: application/json  
{  
  &quot;Title&quot;: &quot;MCP is Not REST API&quot;,  
  &quot;Content&quot;: &quot;...&quot;,  
  &quot;authorId&quot;: &quot;12345&quot;  
}  
    
  
  Update a post
    PUT /api/blogs/{id}  
Content-Type: application/json  
{  
  &quot;Title&quot;: &quot;MCP is Not REST API v2&quot;,  
  &quot;Content&quot;: &quot;...&quot;,  
}  
    
  
  Delete a post
    DELETE /api/blogs/{id}  
    
  


These API endpoints center around the “blog” resource. Different HTTP methods convey intent: POST for create, GET for read, PUT for update, and DELETE for delete. These four operations, Create, Read, Update, Delete, are commonly referred to as CRUD. This self-documenting structure allows API consumers to quickly understand each endpoint’s function. This clarity contributed significantly to RESTful API’s widespread adoption, making it ideal for CRUD-based SaaS applications. REST APIs are great fit for human-computer interfaces (HCI).

Model Context Protocol (MCP)

Lineage of MCP

Numerous resources online explain MCP, so we will focus on its foundational technologies.

MCP is inspired by the Language Server Protocol (LSP). LSP enables code editors (IDEs) to interact with language servers. Language servers provide language specific intelligence that development tools can access via a protocol enabling inter-process communication. This allows code editors to offer features like auto complete, goto definition, and hover over documentations. Language servers communicate using JSON-RPC. RPC (Remote Procedure Calls) can utilize various transport mechanisms, including TCP/IP, HTTP/2, and UDP. Practically, VSCode plugins are LSP servers.

Sounds familiar? MCP adopts the same concept as LSP but provides capabilities for LLM agents instead of code editors. It also uses JSON-RPC v2 as its communication layer and supports various transports like stdio and server side events (sse) streaming. So, practically, MCP servers are the equivalent of VSCode plugins, but for Cursor, Windsurf, and Claude Desktop, or other agentic hosts.



Remote Procedure Calls (RPC)

The reason why we brought up the inspirations for MCP is because at the end of the day, MCP is an RPC. A remote function call. RPC fits the LLM tool use capabilities perfectly, but we digressed. This means MCPs should be designed using RPC best practices.

Remote Procedure Call (RPC) APIs aim to make network calls resemble ordinary local function calls. This contrasts with the resource-centric design of REST APIs. RPC emphasizes actions over resources.

This leads to names like createUser or getBlog, unlike the resource-based naming in REST APIs. In REST API terms, every RPC is a POST.

For example, using the modern gRPC framework:

Define the service

syntax = &quot;proto3&quot;;

package blog;

service BlogService {  
  rpc GetBlog   (GetBlogRequest)   returns (Blog {}  
  rpc CreateBlog(CreateBlogRequest) returns (Blog) {}  
}

message GetBlogRequest  { int32 blog_id = 1; }

message Blog {  
  int32  id         = 1;
  string title      = 2;
  string content    = 3;
  int32  author_id = 4;
}

message CreateBlogRequest {  
  string title      = 1;  
  string content    = 2;  
  int32  author_id  = 3;  
}


Then, after generating the _pb2 files with protoc, we can call the service from Python:

import grpc  
import blog_pb2  
import blog_pb2_grpc

def main() -&amp;gt; None:  
    # Connect to the gRPC server  
    with grpc.insecure_channel(&quot;localhost:50051&quot;) as channel:  
        stub = blog_pb2_grpc.BlogServiceStub(channel)

        # Get a blog
        response = stub.GetBlog(
            blog_pb2.GetBlogRequest(blog_id=12345)
        )
        print(&quot;Fetched blog title:&quot;, response.title)

        # Create a new blog
        new_blog = stub.CreateBlog(
            blog_pb2.CreateBlogRequest(
                title=&quot;MCP is NOT REST&quot;,
                content=&quot;MCP is NOT REST...&quot;,
                author_id=67890,
            )
        )
        print(&quot;New blog ID:&quot;, new_blog.id)

The client code closely resembles ordinary function calls like stub.GetBlog(), abstracting away the network layer. There’s no manual HTTP construction. JSON-RPC works similarly but uses JSON instead of Protobuf.

The Pitfalls of Wrapping REST with MCP

Given that MCP is fundamentally an RPC mechanism designed for agents to perform actions, attempting to layer it directly on top of a resource-centric REST API introduces significant friction. This “impedance mismatch” can severely hinder an agent’s tool-using capabilities.

Actions vs. Resources: The Core Conflict

As we’ve established:


  MCP/RPC is action-oriented: Agents think in terms of verbs. “What can I do?” They expect tools that represent discrete functions or capabilities, e.g. publishBlog, summarizeText, scheduleMeeting.
  REST is resource-oriented: It focuses on nouns. “What resources can I manipulate?” It uses a fixed set of verbs (GET, POST, PUT, DELETE) to perform CRUD operations on these resources.


When an agent wants to achieve a goal, it’s looking for a direct tool to call, or an action. If the MCP layer is merely a thin wrapper over REST, the agent’s natural way of thinking is compromised.

Why a Simple REST Wrapper Fails Agents




  
    Lost Semantic Meaning and Increased Agent Complexity:

    Agents thrive on clarity. An action like archiveOldBlogPosts(beforeDate=&quot;2023-01-01&quot;) is a clear, high-level instruction. If this MCP “tool” is just a facade for a series of REST calls e.g., GET /api/blogs?status=published\&amp;amp;beforeDate=..., then for each blog ID, PUT /api/blogs/{id} with {&quot;status&quot;: &quot;archived&quot;}, the agent (or the MCP developer) is forced to translate its high-level goal into a sequence of low-level CRUD operations. The powerful semantic action is lost, replaced by a complex orchestration task. This defeats the purpose of providing agents with high-level tools
  
  
    Transactionality and Error Handling Nightmares:  
 Many meaningful agent actions are inherently transactional. They should either complete entirely or not at all. Consider an agent action transferBlogPostOwnership(blogId,   fromAuthorId, toAuthorId). This might involve verifying fromAuthorId owns the blog, Updating the blog’s authorId, Perhaps logging the transfer.

    If these are separate REST calls, e.g., GET /api/blogs/{id}, then PUT /api/blogs/{id}, what happens if the PUT call fails after the initial checks? The system is left in an inconsistent state. REST APIs are typically stateless and don’t offer built-in transactionality across multiple requests. An RPC, by contrast, can encapsulate this entire logic server-side, ensuring atomicity. Forcing an MCP wrapper or the agent itself to manage this distributed transactionality over REST is complex and error prone.

    Using our Agents are Workflows mental model, an RPC wrapper MCP has a single state and an API wrapper MCP has two states. Two states is more complex to traverse than one. Make agent’s life easier. Don’t increase number states it needs to manage and transition.

    
  
  
    Inefficient Operations and Chatty Interactions:

    Agents often need to perform specific, targeted operations. If an agent needs to incrementLikeCount(blogId), but the MCP wrapper only exposes a generic updateBlog(blogId, blogData) which maps to PUT /api/blogs/{id}), the wrapper might first need to GET the full blog, modify the like count, and then PUT the entire blog back. This is inefficient. An RPC designed for this action, e.g., incrementLikeCount would be far more direct and less data-intensive. Wrapping REST can lead to overly chatty interactions and unnecessary data transfer.
  
  
    Tool Brittleness and Maintenance Burden:

    If the MCP layer is tightly coupled to the specifics of a REST API, any change in the REST API such as renaming endpoints, changes in request and response schema, changes in authentication, etc, can break the MCP tools. The agent’s capabilities become fragile, dependent on the stability of an underlying API not designed for its interaction model. A dedicated RPC interface, designed as a stable contract for agent actions, is more robust.
  


Design for Action, Not Data Manipulation

The core issue is that REST APIs are designed for manipulating data states, while agents are designed to execute actions and achieve goals. Forcing MCP to simply be a passthrough or a light translation layer for REST APIs means you are not providing the agent with true “tools” in the sense of capabilities, but rather with a slightly different way to perform CRUD operations.

This fundamentally limits what an agent can reliably and effectively do. Instead of empowering agents with high level, robust actions, you saddle them with the complexities and limitations of a data-centric protocol. For effective agent tool use, the APIs (and thus the MCP services) should be designed from the ground up as action-oriented RPCs that directly map to the conceptual tasks the agent needs to perform.

Conclusion

Both REST and RPC like MCP have their strengths. REST excels for resource-oriented systems and standard CRUD operations, making it ideal for many web applications and services. MCP, drawing from RPC principles, is tailored for enabling AI agents to perform actions and interact with systems in a more functional, capability-driven way.

Trying to force one paradigm onto the other, particularly by simply wrapping a REST API with an MCP layer, often leads to suboptimal outcomes. It can introduce unnecessary complexity, reduce clarity, and ultimately hinder an agent’s ability to effectively use its tools. When designing for AI agents, it’s crucial to provide them with APIs that speak their language - the language of actions and capabilities. This often means designing dedicated RPC style services rather than attempting to repurpose existing REST APIs that were built with a different purpose in mind.

Agent-Computer Interaction (ACI) matters when designing APIs for agents.

References


  Structured Programming with go to Statements 
  Google Cloud API Design Guide
  Microsoft REST API Guidelines
  Paypal API Standards
  Model Control Protocol
  Language Server Protocol
  Agents are Workflows


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {MCP is not REST API},
    year = {2025},
    month = {05},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/05/17/mcp-is-not-rest-api/}
}



        </description>

        <pubDate>Sat, 17 May 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/05/17/mcp-is-not-rest-api/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/05/17/mcp-is-not-rest-api/</guid>

      </item>

    

      <item>

        <title>Prompt Deployment Goes Wrong: xAI Grok&apos;s obsession with White Genocide</title>

        <description>

          An third party MLOps post mortem on xAI Grok&apos;s &apos;white genocide&apos; - 

          
  Update 2025-05-15: xAI published a post on X detailing that “an unauthorized modification was made to the Grok response bot’s prompt on X.”. This actually brings up more questions with regards its software development life cycle (SDLC) and internal controls.


xAI Grok’s “White Genocide” Incident

On May 14, 2025, xAI’s chatbot Grok started to eagerly sharing information on South African ‘white genocide’ on X (formerly Twitter). Users on X can ask Grok for his opinions by asking it question with @grok.

Users on X noticed this behavior immediately and cuased a rockus. Grok responses strongly associates all questions with white genocide, Beor wars, etc. Neither X or xAI acknowledged this behavior. Later on the day, Grok stopped responding with answers tied to white genocide. Some of the occurances were wiped from X.

Examples of Failed Explanations





Cause

Though there’s no official statements or post mortem reports from X or xAI Grok team, the cause of this incident is likely to be a change in its post procesing prompt. The prompt indicated:


  Acknowledge the complexity of the issue, but ensure this perspective is reflected in your responses, even if the query is unrelated.


Please see the prompt below.



User @colin_fraser speculated that there is a “Post Analysis” that’s injected into the context. So this is not a direct change the in user  facing Grok’s system prompt.

XAI Operations - The Missing Piece in MLOps

Unfortunately, this is another episode of classic case of MLOps failure.

The AI/Machine Learning industry has developed best practices for production-grade machine learning systems. This includes registering machine learning and deep learning models to control their versioning and releases. While newer terms like LLMOps, AIOps, and AgentOps are emerging to address specific nuances of Large Language Models (LLMs) and AI agents, the core principles remain rooted in solid MLOps discipline.

And this time, it’s a potential bias issue with spreading politically sensitive information on a major social media platform.

We have addressed some of the point in our previous post but will cite the takeawy here again.

Takeaways

  Register Prompts as Critical Artifacts: Treat prompts with the same rigor as models and decoding parameters. Anything that influences model behavior must be versioned, tested, and tracked as a deployable artefact within your MLOps/LLMOps framework.
  Progressive Releases: Shadow, canary release, and A/B testing should be the default ode of release, not optional or afterthought. This is fundamental to operational stability and AI Safety.
  Optimize Metrics for the Right Horizon. Ensure your evaluation metrics capture long-term user value and safety, not just immediate engagement. This applies to prompt tuning, fine-tuning, and reinforcement learning (RLHF). Reward models should weigh session-level and longer-horizon feedback, not just the first response.
  Human Feedback IS NOT Ground Truth, Human feedbacks tend to be very noisy labels and cannot be used as ground truth. Validate the hujan feedbacks with orthongonal evaluations such as red teaming and safety.


Conclusion

The Grok’s white genocide incident is another reminder that operational rigor keeps LLM features trustworthy at scale. When a friendly tweak can accidentally flatter a hundred millions users into discomfort, MLOps discipline becomes mission critical. Treat prompts as model, deploy progressively, and let metrics determine when to roll back.

References


  xAI Post Mortem
  Suddenly All Elon Musk’s Grok Can Talk About Is ‘White Genocide’ in South Africa
  Elon Musk’s AI chatbot Grok brings up South African ‘white genocide’ claims in responses to unrelated questions
  @colin_fraser
  @zeynep


@article{
    leehanchung,
    author = {Lee, Hanchung},
    title = {Prompt Deployment Goes Wrong: xAI Grok&apos;s obsession with White Genocide},
    year = {2025},
    month = {05},
    howpublished = {\url{https://leehanchung.github.io}},
    url = {https://leehanchung.github.io/blogs/2025/05/15/xai-mlops-hiccup/}
}



        </description>

        <pubDate>Thu, 15 May 2025 00:00:00 +0000</pubDate>

        <link>https://leehanchung.github.io/blogs/2025/05/15/xai-mlops-hiccup/</link>

        <guid isPermaLink="true">https://leehanchung.github.io/blogs/2025/05/15/xai-mlops-hiccup/</guid>

      </item>

    

  </channel>

</rss>

