Beyond the Buzzwords

Publish at: 23 min read

Beyond the buzzwords

Modern engineering has become extremely good at making ordinary mechanisms sound profound and inaccessible. Give a process a few more layers, add a hosted dashboard, place a protocol in the middle, wrap the infrastructure in a product name, and suddenly a small system begins to carry the posture of an industry.

The tools themselves are not the problem. The performance around them is. We describe simple interactions in language so inflated that the words begin to outrank the mechanics. A container is no longer a process with packaging. It becomes part of a platform story. A few services and some configuration become architecture. A script with side effects becomes orchestration. The naming grows faster than the complexity.

That inflation has a cost. Engineers begin to treat tools as territories instead of interfaces. Entire clusters of technology are discussed as if they require initiation rites before anyone is allowed to touch them. And so a lot of modern IT acquires a sense of difficulty and prestige it does not fully deserve.

From curves to a prediction #

Long before machine learning[3] became a product category, there was a simpler and more honest instinct: take a collection of numbers, look for a pattern, and try to say something about what comes next.

One of the purest versions of that instinct appears in interpolation. Give mathematics a finite set of points and it will happily produce a curve that passes through all of them. Lagrange polynomial interpolation is one of the cleanest examples. The promise is seductive. Hand me the numbers and I will give you back a formula that touches every one of them exactly.

Lagrange interpolation

y
^
|                          *
|                    .-''' |
|                .-''      |
|            .-''          *
|        .-''            .'
|    *-''             .-'
|      '.          .-'
|        '-.    .-'
|           *--'
+---------------------------------> x

The curve is constructed to pass through every known point.

That exactness feels like intelligence at first. It looks like the problem has been solved because the curve agrees with the data at every known point. But the feeling does not last very long. As the dataset grows, the polynomial grows with it. The expression becomes less pleasant, the behavior between points becomes less trustworthy, and the exact fit starts to look like obedience rather than understanding. The curve has learned to pass through the data. That does not mean it has learned how the data behaves.

So we simplify. We stop demanding that the model honor every point exactly and instead ask for something more useful: a shape that captures the overall tendency. A straight line is the humblest version of that move. Rather than worshipping the dataset, it summarizes it. The mental shift behind regression lives in that difference. Instead of asking for perfect agreement, we ask for a model stable enough to say something about values we have not seen yet.

Linear regression

y
^
|                         *
|                      .-'
|                   .-'
|                .-'
|             .-'      *
|          .-'
|       .-'   *
|    .-'
| *-'
+---------------------------------> x

The line does not pass through every point.
It captures the overall direction of the data.

Linear regression[4] works by choosing the line that best balances the whole dataset instead of satisfying each point individually. In the simplest case, that line has the form:

y = a x + b

Here a is the slope and b is the intercept. Once those two numbers are chosen, every x value produces a predicted y.

The important question is how to choose a and b. The usual answer is: pick the line that makes the prediction errors as small as possible across the entire dataset. For every observed point, the model produces a predicted value on the line. The difference between the observed value and the predicted value is called the residual.

residual = observed value - predicted value

If some residuals are positive and some are negative, they can cancel each other out. So linear regression usually squares them before adding them together. That gives a single quantity to minimize:

sum of squared residuals = Σ (y_observed - y_predicted)^2

This is the core idea. The best-fit line is the one that makes that total error as small as possible.

y
^
|                         *   | residual
|                      .-'    |
|                   .-'       |
|                .-'          |
|             .-'      *      |
|          .-'        |       |
|       .-'   *       |       |
|    .-'              |       |
| *-'                  residual
+---------------------------------> x

That is why regression is more useful than exact interpolation for many real datasets. Rather than memorizing every bump and wobble, it tries to capture a trend that survives the noise. In machine learning language, this is the move from exact fit toward generalization, but the idea is older and simpler than the modern label.

Neural networks and language models #

Linear regression has its own limitations. Take a small image, 32 x 32 pixels, monochrome. That already gives you 1024 points, and the result is still only an icon. For larger datasets, regression struggles not only because of performance, but because a single straight line is too weak a shape to capture richer structure.

That does not restore any mystery. It only means that the model needs more expressive machinery. The underlying instinct is still the same one: learn from examples, reduce error, and turn structure into prediction. This is where another way of learning from data helps: neural networks.

At first glance, a neural network looks much more exotic than a regression line. In reality, it is built from the same instinct. You still start with inputs, you still multiply them by parameters, you still add things together, and you still compare prediction against reality. The difference is that you stop doing this only once.

Instead of one straight line, you create many small computational units arranged in layers. Each unit receives numbers, applies weights to them, adds a bias term, and then usually passes the result through a simple nonlinear function. The output of one layer becomes the input of the next.

input layer        hidden layer        output layer

x1 ----\             h1 ----\
x2 ----- > ( ) ----> h2 ----- > ( ) ----> y
x3 ----/             h3 ----/

The picture sounds biological because of the word neuron, but nothing biological is really happening here. Each unit is just a tiny function:

z = w1x1 + w2x2 + ... + wnxn + b
output = activation(z)

The weights decide how strongly each input matters. The bias shifts the result up or down. The activation function is what prevents the whole network from collapsing into one large straight line. Without that nonlinearity, stacking many layers would still behave like a single linear transformation, which would defeat the point.

So the real upgrade over regression is composition. Regression gives you one simple shape. A neural network composes many simple shapes into a more flexible function.

Training a neural network is still the same old discipline in a more layered form. Show it examples. Let it produce a prediction. Measure the error. Adjust the weights so the next prediction is slightly better. Repeat this many times.

input -> prediction -> error -> weight update -> better prediction

The adjustment step is usually done with gradient descent and backpropagation. Those names sound heavier than the mechanism. Gradient descent means "move the parameters downhill on the error surface." Backpropagation means "compute how much each weight contributed to the error so you know which one to change." The mathematics can become large, but the idea is not mysterious.

current weights
      |
      v
 make prediction
      |
      v
 compare with expected result
      |
      v
 compute error
      |
      v
 adjust weights a little
      |
      v
 repeat

That is why neural networks scale better to images, speech, and language than plain linear regression. Instead of matching one line to a cloud of points, they learn layered transformations that can represent much more complicated relationships.

Once that is clear, a language model becomes less intimidating as well. Underneath the interface sits the same kind of weighted computational network trained to reduce prediction error. The main difference is the domain. Instead of predicting a numeric target from a few variables, it predicts the next token in a sequence.

Underneath the friendly chat interface, a large part of that work is matrix multiplication. Tokens are converted into vectors. Those vectors are pushed through layers of learned weight matrices. Each multiplication mixes and transforms the signal, producing new internal representations that carry information about meaning, position, and context. By the time the signal reaches the output layer, the model produces a score for every possible next token. Those scores are converted into probabilities, and the next token is chosen from there.

text -> tokens -> vectors -> matrix multiplications ->
scores -> probabilities -> next token

"the sky is"
      |
      v
   ["the", "sky", "is"]
      |
      v
   embeddings
      |
      v
   W1 * x
      |
      v
   W2 * h1
      |
      v
   ...
      |
      v
   scores for all possible next tokens
      |
      v
   probability distribution
      |
      v
   "blue"

So yes, one valid low-level description of an LLM[5] is this: a machine that keeps multiplying large matrices of learned weights in order to update probability distributions over the next token. That sounds less glamorous than "artificial intelligence", but mechanically it is much closer to the truth.

At the lowest useful level of description, an LLM is a trained function that assigns probabilities to possible next tokens given the context so far. Everything else people say about it sits on top of that mechanism.

AI agent #

Once that part is clear, the word agent becomes much less theatrical.

An AI agent[6], stripped of the theater, is a control loop wrapped around a model.

The loop is simple enough:

input -> model -> decision -> tool call -> result -> next decision
           +-------------------+
           |   user / input    |
           +---------+---------+
                     |
                     v
           +-------------------+
           |       agent       |
           |   control loop    |
           +---------+---------+
                     |
          +----------+----------+
          |                     |
          v                     v
+-------------------+   +-------------------+
|        LLM        |   |       tools       |
| next-step advice  |   | MCP / APIs / CLI  |
+---------+---------+   +---------+---------+
          |                       |
          +----------+------------+
                     |
                     v
           +-------------------+
           |   state update    |
           | continue or stop  |
           +-------------------+

The model helps interpret the current state and suggest what to do next. The loop decides whether that suggestion is enough, whether a tool should be called, what to do with the result, and when the task is complete. Behind the grand phrase lies little more than that.

When people say AI agent, the practical meaning is often much smaller than the mood around the term. A program receives input, asks a language model to reason about it, maybe calls a tool, checks what came back, and continues. In practice, the whole arrangement remains control flow with a probabilistic model inside the loop.

If you want a more old-fashioned way to think about it, the shape is not far from a state machine. The system moves from one state to another based on input, intermediate results, and model output. The probabilities come from the model, but the overall structure is still transition and control.

This matters because the rest of the stack enters only after this loop exists. The model on its own can describe, classify, summarize, and predict. The agent is what gives that model the ability to act.

The game with numbers #

Most modern talk about machine learning assumes the same order of events. First collect the dataset. Then fit the model. Then ask the model for an answer. Data first, prediction second. That sequence is so familiar now that it begins to feel like the only respectable way prediction can work.

This experiment is built on the opposite order. The prediction is fixed in advance, and the dataset arrives only afterwards. That reversal matters because it strips away some of the theatrical framing immediately. Once the order becomes prediction first, data second, the whole exercise reads less like inspired guesswork and more like a structured test.

The game starts with a collection of numbers that are supposed to feel random. They are handed to the local system as they arrive. No cleanup. No retries. No reshaping the input after the fact.

dataset -> model -> prediction

But here the order is reversed:

prediction -> dataset -> verification

Somewhere else, a prediction already exists, and only the agent knows what it is.

That reversal is what makes the trick work.

What exists in advance is not a specific number waiting inside an envelope, but a hypothesis about the shape of the dataset before the dataset arrives.

So the experiment is simple. A pile of supposedly random numbers is handed to the system. The agent already knows the secret condition. As the sample grows, the system asks one narrow question: do these observations fit the hidden pattern strongly enough to count as a hit?

If they do, the system publishes SUCCESS. If they do not, it publishes FAILURE.

Only later is it worth opening the box and showing exactly what statistic was being tested and why it works.

This is where the agent becomes useful. The job is no longer only to talk about the pattern. The job is to receive the numbers, verify whether the prediction holds, and trigger the next action based on the result.

If the prediction is correct, the system should make that success visible. If the prediction fails, the system should make that failure visible. The output is intentionally small:

SUCCESS

or

FAILURE

Everything else that follows exists only to carry that tiny outcome through a much louder stack. The remaining vocabulary enters here: tool protocols, remote infrastructure, infrastructure as code[7], clusters, containers, and a web page. One by one, each of them turns out to be smaller than its reputation.

Giving the agent hands #

At this point the local agent can reason about the numbers and decide what should happen next. But deciding is not the same thing as acting. A model can suggest. An agent can loop. Neither of those facts alone creates infrastructure, starts workloads, or publishes a result.

So the first practical problem is simple: how does the agent reach outside itself?

This is where tool access appears.

MCP is best understood as a protocol. Framed that way, it becomes both less glamorous and more useful. A protocol is simply an agreement about how capabilities are described, how they are called, and how results come back. In this case, it gives the agent a structured way to discover tools and invoke them without hardwiring every integration by hand.

agent -> MCP tool -> remote action -> result -> agent

Nothing more dramatic is happening there. Speaking through a protocol does not grant the agent special powers; it makes the system orderly.

Once that boundary exists, the rest of the stack becomes easier to name.

From local decision to remote effect #

The agent runs locally. The visible result does not.

That means the decision made on the laptop has to travel outward into remote infrastructure. This is the point where AWS enters the story.

People often talk about AWS as if it were a remote force of nature hovering above engineering. In practice, it is commodity infrastructure exposed through APIs. Compute, storage, networking, load balancers, DNS records, managed databases, clusters. The scale is impressive, but the interaction is plain. You ask for resources. The platform allocates them. You configure them. You pay for them.

The relationship is still straightforward:

local agent -> tool call -> AWS API -> workload update -> visible result

If the prediction is verified, the remote side should expose SUCCESS. If it fails, it should expose FAILURE.

AWS, CDK, and Kubernetes #

Once people hear those three names together, they often stop imagining mechanics and start imagining ceremony. But the roles here are actually narrow.

AWS is simply where the remote resources live. The useful demystification is this: cloud is rented infrastructure with a programmable interface.

CDK describes those resources in code instead of through a console. In other words, infrastructure becomes a program. Write the desired shape once, let the platform translate it into actual cloud objects, and stop pretending that clicking through menus is a higher form of engineering.

Before Kubernetes can do anything useful, there needs to be something to run. That unit is the container.

A container is a process packaged with its runtime dependencies, filesystem view, environment, and isolation boundaries. The important word there is still process. The package makes it portable and repeatable, but the thing doing the work is a program running in a controlled environment.

Kubernetes[8] is where that packaged process gets orchestrated. Again, the intimidating part is mostly the vocabulary. Underneath that, it is a control system that tries to make reality match a declared state. Give it a container image, a deployment description, and a service definition, and it keeps trying to make those declarations true.

The key mental shift is simple. With a raw process, you start it directly. With Kubernetes, you describe what should be running and let controllers reconcile the gap between desired state and actual state.

desired state:
  - run 1 container
  - expose it as a service

actual state:
  - container crashed

kubernetes:
  - notice mismatch
  - start new container
  - restore desired state

In this setup, the cluster already exists. The problem is only to tell it which tiny result to show.

agent
  |
  v
MCP tool
  |
  v
AWS API
  |
  v
Kubernetes cluster
  |
  v
container
  |
  v
web page: SUCCESS / FAILURE

The chain is long enough to sound impressive at a meetup, yet it remains only a chain.

Container and web page #

The last two pieces are the least mysterious of all.

A container is just a packaged process with a predictable runtime environment. In this case the process serves a tiny web application: a small program prepared well enough to run the same way in more than one place.

And the web application itself is almost insultingly small. It does not need business logic, recommendation systems, observability dashboards, or three frontend frameworks fighting over hydration. It only needs to show the result.

+------------------+
|     SUCCESS      |
+------------------+

or

+------------------+
|     FAILURE      |
+------------------+

All of those layers have been carrying the same punchline. A local model may help normalize the input, but the prediction itself is checked by an ordinary deterministic rule. The agent uses a tool protocol to trigger a remote action. Cloud infrastructure routes the outcome into a cluster. A container serves a page. The page shows one word.

And suddenly the buzzwords begin to look a lot less sacred.

Wiring it up #

With the vocabulary reduced to mechanics, the implementation stops looking like a special event. Here is what actually ran:

  • a local Node.js and TypeScript program
  • optional local Ollama for number extraction from messy text
  • a real MCP server over stdio
  • AWS CDK provisioning one EC2 instance
  • a single-node k3s cluster on that instance
  • one nginx:alpine container
  • one ConfigMap, one Service, one Ingress
  • a public page and result.json

Written as a stack, that sounds like six disciplines. In execution, it is one pipeline.

First, the local machine receives the numbers.

Second, if the input is plain text, a small deterministic extractor can parse the numbers directly. If the input is messy human phrasing, a local LLM can help extract them. Either way, the verification step itself stays deterministic.

Third, the local program checks the prediction, produces a structured result, and crosses the tool boundary through MCP.

Fourth, the MCP tool publishes that result to AWS through SSM.

Fifth, the remote side updates a ConfigMap in k3s, restarts a tiny deployment, and waits for the rollout.

Sixth, the nginx container serves the updated result page.

Architecture view

+--------------------------+            +-----------------------------------+
|      Local machine       |            |                AWS                |
|                          |            |                                   |
|  +--------------------+  |            |  +-----------------------------+  |
|  |      CLI app       |--+----------->|  |   EC2 instance with k3s     |  |
|  |    agent loop      |  |    MCP     |  +-------------+---------------+  |
|  +---------+----------+  |            |                |                  |
|            |             |            |                v                  |
|  +---------v----------+  |            |  +-----------------------------+  |
|  |   regex / Ollama   |  |            |  | ConfigMap + Deployment +    |  |
|  |  number extractor  |  |            |  | Service + Ingress           |  |
|  +--------------------+  |            |  +-------------+---------------+  |
+--------------------------+            |                |                  |
                                        |                v                  |
                                        |  +-----------------------------+  |
                                        |  |    nginx:alpine container   |  |
                                        |  +-------------+---------------+  |
                                        |                |                  |
                                        |                v                  |
                                        |  web page + result.json         |
                                        +-----------------------------------+

And over time, the same system behaves like this:

Sequence view

User        CLI        Extractor      MCP         SSM/k3s       Web page
 |           |            |            |             |             |
 | numbers ->|            |            |             |             |
 |           | parse ---->|            |             |             |
 |           |<-- nums ---|            |             |             |
 |           | verify     |            |             |             |
 |           | publish ----------------------------->|             |
 |           |            |            |   update    |             |
 |           |            |            | ConfigMap   |             |
 |           |            |            | restart     |             |
 |           |            |            | deployment  |-----------> |
 |           |            |            |             | SUCCESS /   |
 |           |            |            |             | FAILURE     |

At the level that matters first, the implementation is only this sequence. The entertaining part is how grand it can be made to sound once the nouns are expanded. The sequence itself remains completely ordinary.

Local verification #

The local side is where the whole thing remains honest.

There is no cloud logic here at all. Just Node.js, TypeScript, one optional local model, and a deterministic verification rule.

This is the point where the hidden rule is finally revealed.

The hypothesis the agent was carrying from the beginning is this: in the observed sample, the leading digit 1 should appear often enough to count as a hit.

That idea is loosely inspired by first-digit statistics such as Benford's law[2], where the leading digit 1 appears more often than naive intuition suggests. But the implementation here is not trying to perform a formal Benford test. It uses the same basic feature, the leading digit, and turns it into a much simpler binary classifier for the sake of the demonstration.

The mathematics behind that rule is small but exact. For each observed number n_i, define an indicator:

I(n_i) = 1, if the leading digit of n_i is 1
I(n_i) = 0, otherwise

Now take the average of those indicators across the whole sample:

leadingOneRatio = (1 / N) * Σ I(n_i)

This works because the mean of 0/1 indicator variables is exactly the observed frequency of the event they represent. Every number that begins with 1 contributes 1. Every other number contributes 0. Add them up, divide by the sample size, and you get the proportion of leading ones in the dataset.

If this were a formal Benford-style calculation, the reference value for leading 1s would be P(1) = log10(2) ≈ 0.301, not 0.7. That is an important distinction. The implementation here intentionally uses a much stronger cutoff because the goal is not statistical rigor; the goal is to stage a visible pass/fail outcome with a tiny sample.

For that reason, the decision rule takes the form:

if leadingOneRatio >= threshold
  prediction = verified
else
  prediction = failed

The threshold is not a law of nature. It is simply the chosen cutoff for this stunt: a coarse boundary between "the pattern is present strongly enough for this demonstration" and "it is not." With only eight numbers in the successful example, this should be read as a toy classifier rather than a serious inferential procedure. The point is not that the threshold is sacred, but that the entire verdict comes from a transparent statistic.

export const verifyPrediction = (
  numbers: number[],
  threshold = 0.7,
) => {
  const leadingOnes = numbers.filter((value) => leadingDigit(value) === 1).length;
  const leadingOneRatio = numbers.length === 0 ? 0 : leadingOnes / numbers.length;
  const verified = leadingOneRatio >= threshold;

  return {
    prediction: verified ? "verified" : "failed",
    page: verified ? "SUCCESS" : "FAILURE",
    message: verified
      ? "Most numbers begin with 1"
      : "Observed numbers did not match the expected pattern",
    numbers,
    leadingOnes,
    leadingOneRatio,
    threshold,
  };
};

That one function produces the payload the rest of the system carries around. In the successful branch, it looks like this:

{
  "prediction": "verified",
  "page": "SUCCESS",
  "message": "Most numbers begin with 1",
  "numbers": [12, 145, 18, 1099, 31, 176, 102, 87],
  "leadingOnes": 6,
  "leadingOneRatio": 0.75,
  "threshold": 0.7
}

In the failure branch:

{
  "prediction": "failed",
  "page": "FAILURE",
  "message": "Observed numbers did not match the expected pattern",
  "numbers": [23, 34, 45, 56, 67, 78, 89, 92],
  "leadingOnes": 0,
  "leadingOneRatio": 0,
  "threshold": 0.7
}

The language model is optional and narrowly scoped. It does not decide the result. It only helps extract numbers when the input arrives wrapped in human phrasing:

export const createOllamaExtractor = (options: { model: string; baseUrl?: string }) => {
  const baseUrl = options.baseUrl ?? "http://127.0.0.1:11434";

  return async (rawInput: string) => {
    const response = await fetch(`${baseUrl}/api/chat`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        model: options.model,
        stream: false,
        messages: [
          {
            role: "system",
            content: "Extract all numbers from the user input. Return JSON only in the shape {\"numbers\":[...]} with numeric values.",
          },
          { role: "user", content: rawInput },
        ],
      }),
    });

    const payload = await response.json() as { message?: { content?: string } };
    const parsed = JSON.parse(payload.message?.content ?? "{}") as { numbers?: unknown[] };

    return (parsed.numbers ?? [])
      .map((value) => Number(value))
      .filter((value) => Number.isFinite(value));
  };
};

This is the demystification in one place. The LLM is not the judge; it acts only as an input normalizer.

The actual decision procedure is closer to statistics than spectacle. One feature is extracted from each number, a ratio is computed, and the result is classified against a threshold. In the older language of statistical learning, that already counts as a model, albeit a very small and very explicit one.

The so-called agent is smaller still:

export const runAgent = async (
  rawInput: string,
  extractNumbers: NumberExtractor,
  publish: Publisher,
  threshold = 0.7,
) => {
  const numbers = await extractNumbers(rawInput);

  if (numbers.length === 0) {
    throw new Error("No numeric input found");
  }

  const result = verifyPrediction(numbers, threshold);
  await publish(result);
  return result;
};

No digital soul appeared. A small program extracted numbers, checked a rule, and published the outcome.

The MCP boundary #

The MCP side is equally ordinary. The local process starts a small server over stdio and exposes one tool: publish_result.

const server = new McpServer({
  name: "beyond-buzzwords-publisher",
  version: "1.0.0",
});

server.tool(
  "publish_result",
  {
    prediction: z.enum(["verified", "failed"]),
    page: z.enum(["SUCCESS", "FAILURE"]),
    message: z.string(),
    numbers: z.array(z.number()),
    leadingOneRatio: z.number(),
    threshold: z.number(),
  },
  async (input) => {
    await publisher(input);

    return {
      content: [
        {
          type: "text",
          text: `Published ${input.page}`,
        },
      ],
    };
  },
);

In practice, a lot of protocol language collapses to exactly that: validate the payload, call a function, return a message.

Remote publish #

The remote half is where the big nouns finally have to justify themselves.

CDK provisions one EC2 instance. Its user data installs k3s, creates a namespace, bootstraps a ConfigMap, and deploys one nginx:alpine container behind a Service and Ingress.

At the code level, the dramatic phrase "infrastructure as code" reduces to ordinary TypeScript constructing cloud objects and attaching startup commands:

const vpc = new ec2.Vpc(this, "Vpc", {
  maxAzs: 2,
  natGateways: 0,
  subnetConfiguration: [
    {
      name: "public",
      subnetType: ec2.SubnetType.PUBLIC,
      cidrMask: 24,
    },
  ],
});

const securityGroup = new ec2.SecurityGroup(this, "WebSecurityGroup", {
  vpc,
  allowAllOutbound: true,
});

securityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  "Public HTTP access",
);

const role = new iam.Role(this, "InstanceRole", {
  assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
});

role.addManagedPolicy(
  iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
);

const userData = ec2.UserData.forLinux();
userData.addCommands(
  "set -euxo pipefail",
  "dnf update -y",
  "dnf install -y curl",
  "curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC='--write-kubeconfig-mode 644' sh -s -",
  "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml",
  "until kubectl get nodes >/dev/null 2>&1; do sleep 5; done",
);

new ec2.Instance(this, "WebInstance", {
  vpc,
  vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
  securityGroup,
  role,
  userData,
  instanceType: ec2.InstanceType.of(
    ec2.InstanceClass.T4G,
    ec2.InstanceSize.SMALL,
  ),
  machineImage: ec2.MachineImage.latestAmazonLinux2023({
    cpuType: ec2.AmazonLinuxCpuType.ARM_64,
  }),
});

No ritual is hiding here. One TypeScript program creates a network, a machine, a role, and a startup script.

EC2
  -> k3s
     -> namespace: beyond-buzzwords
     -> ConfigMap: result-site-content
     -> Deployment: result-site
     -> Service: result-site
     -> Ingress: result-site

The publisher does not rebuild images or apply a sprawling chart. It sends a short SSM command sequence to the instance:

const buildCommands = (result: VerificationResult): string[] => {
  const page64 = encodeFile(renderPage(result));
  const json64 = encodeFile(JSON.stringify(result, null, 2));

  return [
    "set -euo pipefail",
    "export KUBECONFIG=/etc/rancher/k3s/k3s.yaml",
    "install -d -m 0755 /tmp/result-site",
    `printf '%s' '${page64}' | base64 -d > /tmp/result-site/index.html`,
    `printf '%s' '${json64}' | base64 -d > /tmp/result-site/result.json`,
    "kubectl -n beyond-buzzwords create configmap result-site-content --from-file=index.html=/tmp/result-site/index.html --from-file=result.json=/tmp/result-site/result.json --dry-run=client -o yaml | kubectl apply -f -",
    "kubectl -n beyond-buzzwords rollout restart deployment/result-site",
    "kubectl -n beyond-buzzwords rollout status deployment/result-site --timeout=180s",
  ];
};

On the cloud side, the work really reduced to three operations:

  • update a ConfigMap
  • restart a deployment
  • wait for the rollout

The container is almost insulting in its simplicity: nginx:alpine serving two files, index.html and result.json. Kubernetes is present, but its role is narrow and literal: keep the tiny result site running and accept content updates.

Both branches were run end to end. One input produced SUCCESS. Another produced FAILURE. The same local program, the same MCP tool, and the same remote infrastructure carried both outcomes.

Once that becomes visible in code, most of the mystification disappears. The final system is not trivial, but neither is it sacred. It consists of one local loop, one tool boundary, one rented machine, one small cluster, one container, and one web page that says either SUCCESS or FAILURE.

Conclusion #

The point of this exercise was never to argue that modern tools are fake or that complexity never exists. Real systems do become difficult. Production environments fail in real ways. Distributed systems still deserve respect. The problem is that the language around them often expands faster than the mechanics underneath.

This project began by reversing the respectable order of modern prediction. Instead of collecting a dataset, fitting a model, and asking for an answer, it started with a hidden prediction and waited for the data to arrive. The rest of the stack followed the same pattern. Each grand term turned out to be a small, inspectable mechanism connected to the next one.

A local model normalized the input. A deterministic statistic verified the result. An agent loop decided what to do next. MCP carried that decision to a tool. CDK provisioned a machine in AWS. k3s kept one small container serving a page that said either SUCCESS or FAILURE. Each step deserves precision. None of them benefits from inflated language.

That is the real complaint behind the title. Buzzwords do not only rename technologies. They encourage people to treat ordinary engineering as ceremony. Once the terms are translated back into probabilities, APIs, processes, configuration, and control loops, the stack becomes easier to reason about and therefore easier to manage.

Source code #

Reference implementation (opens in a new tab)

References

  1. Can Maths Predict the Future? (opens in a new tab)
  2. Benford's law (opens in a new tab) · Back
  3. Machine learning (opens in a new tab) · Back
  4. Linear regression (opens in a new tab) · Back
  5. Large language model (opens in a new tab) · Back
  6. Intelligent agent (opens in a new tab) · Back
  7. Infrastructure as code (opens in a new tab) · Back
  8. Kubernetes (opens in a new tab) · Back