Cloud & DevOps

Learning Machine Learning Engineering on AWS: My Developer Notes

Afzal AhmedFaz Ahmed
·27 July 2026·32 min read
AWSAmazon BedrockSageMaker AIMachine LearningGenerative AIAI AgentsRAGFeature StoreMLOpsPython

Why This Matters

Faz Ahmed’s 5,000-word developer notebook from learning machine learning on AWS—connecting Amazon Bedrock, SageMaker AI, RAG, agents, data engineering, training, deployment, evaluation, security and LLMOps into one understandable journey.

These are my developer notes from learning machine learning engineering on AWS.

I am deliberately calling them notes rather than a definitive guide. I come to this subject as a software engineer who is comfortable with C#, APIs, SQL, Azure, cloud architecture and production support, but who is still building a clear mental model of the AWS machine-learning landscape. I want to record what helped the concepts click, where I initially mixed terms together, and how I now see the services fitting into a production system.

My first observation is that machine learning on AWS is not one service and it is not only model training. It is a lifecycle:

Business problem
  -> data collection and governance
  -> data preparation and feature engineering
  -> model choice or model training
  -> evaluation
  -> deployment and inference
  -> monitoring
  -> feedback and retraining

Generative AI adds prompts, foundation models, embeddings, retrieval, agents and evaluation of non-deterministic answers. The software-engineering concerns remain familiar: identity, networking, tests, deployments, observability, cost and failure recovery. The unfamiliar part is the model behaviour and the data discipline around it.

My first service map: Bedrock versus SageMaker AI

The first question I had was why AWS has both Amazon Bedrock and Amazon SageMaker AI.

My simplified answer is:

Amazon Bedrock
  Use managed foundation models and build generative AI applications.
  Think prompts, model inference, RAG, knowledge bases, guardrails and agents.

Amazon SageMaker AI
  Build and operate the wider machine-learning lifecycle.
  Think notebooks, processing, training, tuning, model registry,
  endpoints, feature stores, pipelines and monitoring.

This is not an absolute boundary. SageMaker AI can deploy and fine-tune large language models. Bedrock supports model customisation and managed AI application capabilities. The distinction is about my starting point.

If I want to add summarisation or a grounded assistant using a managed foundation model, Bedrock is the natural place to investigate. If I have a tabular loan-risk dataset and want to train, tune, deploy and monitor an XGBoost classifier, SageMaker AI gives me the lifecycle tools. If I am operating a specialised model or building a custom training pipeline, SageMaker AI becomes more relevant.

As a .NET developer, I compare Bedrock to consuming managed intelligence through an AWS API. SageMaker AI feels more like an engineering platform around data, compute, model artefacts and deployments. Both integrate with ordinary applications through SDKs and HTTP boundaries.

Foundation models, LLMs and inference

A foundation model is trained on a broad and very large dataset so that it can support many downstream tasks. A large language model is a foundation model focused on language. It can generate, summarise, classify, translate, extract and reason over text depending on its capabilities and instructions.

Training is the expensive process that changes model parameters from data. Inference is using a trained model to generate a prediction or response. This distinction matters because most teams adopting generative AI do not train a foundation model from scratch. They call a managed model during inference and improve the application with prompts, trusted context, tools or targeted customisation.

A prompt is input to the model. Tokens are the smaller units the model processes. A token may be a word, part of a word or punctuation. Token counts matter because models have context limits and inference is often priced by input and output tokens.

Temperature controls randomness. A lower value is useful where consistency matters; a higher value permits more variation. Maximum output tokens limit response length. Other sampling settings, such as top-p, influence which likely tokens the model may choose.

My important note: these settings do not make an unreliable task reliable. A low temperature cannot give a model access to current loan policy. That needs grounding. A longer prompt cannot replace authorisation. Model parameters tune behaviour; application architecture provides control.

Calling Amazon Bedrock from Python

AWS examples frequently use Python and Boto3, the AWS SDK for Python. A simplified Bedrock Runtime call looks like this:

import json
import os

import boto3
from botocore.config import Config


config = Config(
    connect_timeout=3,
    read_timeout=30,
    retries={"max_attempts": 3, "mode": "standard"},
)

client = boto3.client(
    "bedrock-runtime",
    region_name=os.environ.get("AWS_REGION", "eu-west-2"),
    config=config,
)


def summarise_case(case_notes: str) -> str:
    if not case_notes or not case_notes.strip():
        raise ValueError("case_notes must not be empty")

    if len(case_notes) > 20_000:
        raise ValueError("case_notes exceeds the accepted size")

    response = client.converse(
        modelId=os.environ["BEDROCK_MODEL_ID"],
        system=[{
            "text": (
                "You summarise lending case notes. "
                "Do not invent facts. Mark missing information clearly."
            )
        }],
        messages=[{
            "role": "user",
            "content": [{"text": case_notes}],
        }],
        inferenceConfig={
            "temperature": 0.1,
            "maxTokens": 500,
        },
    )

    blocks = response["output"]["message"]["content"]
    text_blocks = [block["text"] for block in blocks if "text" in block]

    if not text_blocks:
        raise RuntimeError("The model returned no text content")

    return "\n".join(text_blocks)

I like this example because it shows that an AI call is still a production integration. It has timeouts, bounded retries, input limits, configuration and response validation. The model ID belongs in configuration. Credentials should come from the AWS credential chain or an IAM role, not source code.

I would also add structured logging around latency, model ID, token usage and a correlation ID. I would not log raw case notes or full responses because they may contain personal or confidential information.

Model choice is an engineering decision

The Bedrock model catalog provides models from AWS and other providers. The mistake would be to select the largest model because it appears most capable.

I need to evaluate:

  • Does the model support my modality: text, image, audio or multiple types?
  • How well does it perform on my real task?
  • What is the input and output token cost?
  • What latency do users tolerate?
  • Which AWS Regions support the model and required features?
  • What context window is available?
  • Does it support tool use, structured output or streaming?
  • What are the provider’s usage terms and lifecycle expectations?
The correct test set is not ten questions invented during a demo. It should contain representative, difficult and risky examples from the intended workload, with sensitive data removed or synthesised where appropriate.

I also need a fallback strategy. A model version can change, reach an end-of-life stage or be unavailable in a Region. The application should isolate model-specific request and response details behind an adapter. That makes evaluation and migration possible without spreading provider assumptions through the codebase.

Prompt engineering and context engineering

Prompt engineering is designing instructions that guide model behaviour. Context engineering is deciding what information the model receives at inference time.

A useful prompt separates responsibilities:

System instruction:
You assist an underwriter. Use only supplied evidence. If evidence is
insufficient, say what is missing. Never approve or reject an application.

Context:
Relevant lending policy passages and authorised case facts.

User request:
Explain which documents remain outstanding for this application.

Output contract:
Return JSON with summary, missingDocuments, evidenceReferences and warnings.

The system instruction sets behaviour. Retrieved context supplies facts. The user asks a question. The output contract makes integration safer.

Structured output still requires validation. A model can return malformed JSON, omit a required field or invent an enum value. Parse into a schema, reject invalid responses and decide whether to retry, repair or fail safely.

Prompt injection is another concern. A retrieved document may contain instructions telling the model to ignore its system prompt or reveal data. Application code must treat external content as data, restrict tool permissions and apply authorisation outside the model. A prompt is not a security boundary.

I would version prompts in source control, review changes and run evaluations before release. Editing a production prompt directly in a console is equivalent to changing application logic without a deployment record.

Embeddings and semantic similarity

An embedding is a numerical vector representing semantic meaning. Texts with similar meaning tend to sit near one another in vector space, even when they use different words.

This explains semantic search. A user asks for “proof of earnings,” while a document uses “income verification.” Keyword matching may miss the relationship; vector similarity can find it.

The process is roughly:

Document
  -> split into chunks
  -> create an embedding for each chunk
  -> store vector plus text and metadata

Question
  -> create question embedding
  -> find nearby document vectors
  -> return relevant chunks

Chunking is more important than I first assumed. A chunk that is too large contains unrelated ideas and wastes model context. A chunk that is too small loses meaning. Overlap can preserve context across boundaries, but too much overlap creates duplicated evidence and cost.

Metadata enables filters. A lending assistant should filter by tenant, jurisdiction, policy version and user permissions before returning passages. Semantic similarity alone does not understand access control.

Embeddings also have a lifecycle. If I change the embedding model, old and new vectors may not be comparable. I need a versioned index and a controlled re-embedding process rather than overwriting production data blindly.

Retrieval-Augmented Generation on AWS

Retrieval-Augmented Generation, or RAG, grounds a model with information retrieved at request time.

User question
  -> validate identity and scope
  -> retrieve authorised evidence
  -> build model context
  -> generate answer
  -> return answer with citations

Amazon Bedrock Knowledge Bases can manage much of the ingestion and retrieval pipeline. Data is connected, processed and indexed so that an application or agent can retrieve relevant knowledge. This can reduce the custom infrastructure needed for a first production RAG system.

RAG does not guarantee truth. Retrieval may return the wrong passage. The correct passage may not be indexed. The model may misunderstand evidence. Evaluation therefore has at least two layers:

  1. Retrieval quality: did we find the right evidence?
  2. Generation quality: did the answer use that evidence correctly?
I want citations because they allow a user to inspect the source, but citations must genuinely support the claim. A link beside a paragraph does not prove that the answer is grounded.

Freshness matters. When a policy changes, ingestion must update the index promptly. Deletion matters too: removing a source document should remove or invalidate its derived chunks and vectors. Data retention and subject-access obligations do not disappear because the data became an embedding.

AI agents: model plus tools plus a loop

My simplest definition of an AI agent is a model operating inside a controlled loop with instructions, state and tools.

Goal
  -> model decides next step
  -> call authorised tool
  -> observe result
  -> decide whether more work is needed
  -> produce answer or stop

A tool may search a knowledge base, call an API, query a database through a safe service or start a workflow. The model does not directly receive broad cloud permissions. It receives narrowly designed capabilities.

For example, an agent should not have a generic ExecuteSql tool. It could have:

def get_application_status(application_id: str) -> dict:
    """Return the current status for one authorised application."""
    # Validate identifier, caller and tenant before querying.
    ...


def list_required_documents(product_code: str) -> list[str]:
    """Return required document types for a supported product."""
    ...

The tool name, description and schema help the model choose it. The implementation enforces authorisation, validates input, limits output and records an audit event.

Amazon Bedrock AgentCore provides managed capabilities for deploying and operating agents. The source material also uses Strands Agents, an open-source SDK for building agents. My takeaway is that an agent framework helps with the loop; AgentCore addresses production concerns such as runtime, identity, memory and observability.

The gotcha is autonomy without boundaries. I would begin with read-only tools, low iteration limits and human approval for consequential actions. Sending a notification may be reversible; transferring funds is not. Agent plans should not bypass business workflows that already enforce separation of duties.

Traditional machine learning still matters

Generative AI receives attention, but many business problems remain prediction and classification problems.

For a loan-risk classifier:

Features:
  income, requested amount, loan-to-value ratio,
  credit-history length, missed-payment count

Label:
  defaulted within agreed period: yes or no

Model output:
  probability or predicted class

Supervised learning uses labelled examples. Unsupervised learning finds structure without known labels, such as clustering similar customers. Reinforcement learning learns through rewards from actions and outcomes.

Training fits a model to a training dataset. Validation helps choose settings. A final test set estimates generalisation on unseen examples. If I repeatedly tune against the test set, it stops being an honest test.

Overfitting means the model learns training-specific noise and performs poorly on new data. Underfitting means the model is too simple or insufficiently trained to capture the pattern.

Accuracy can be misleading. If only one percent of transactions are fraudulent, a model predicting “not fraud” every time is 99 percent accurate and completely useless. I need precision, recall, F1, ROC-AUC or business-specific cost measures depending on the problem.

For lending, evaluation must also consider fairness, explainability, legal constraints and human oversight. A technically strong score does not make a model appropriate for a consequential decision.

SageMaker AI training jobs

SageMaker AI training separates my training code and data from managed compute.

The conceptual flow is:

Training data in S3
  + training image or script
  + hyperparameters
  + IAM execution role
  + selected compute
        -> SageMaker training job
        -> model artefacts written to S3

A simplified XGBoost training script might be:

import argparse
from pathlib import Path

import joblib
import pandas as pd
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from xgboost import XGBClassifier


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--max-depth", type=int, default=5)
    parser.add_argument("--n-estimators", type=int, default=200)
    args = parser.parse_args()

    data = pd.read_parquet("/opt/ml/input/data/training/loans.parquet")

    required = {"income", "requested_amount", "ltv", "defaulted"}
    missing = required.difference(data.columns)
    if missing:
        raise ValueError(f"Missing required columns: {sorted(missing)}")

    features = data[["income", "requested_amount", "ltv"]]
    labels = data["defaulted"]

    x_train, x_test, y_train, y_test = train_test_split(
        features,
        labels,
        test_size=0.2,
        random_state=42,
        stratify=labels,
    )

    model = XGBClassifier(
        max_depth=args.max_depth,
        n_estimators=args.n_estimators,
        eval_metric="logloss",
    )
    model.fit(x_train, y_train)

    predictions = model.predict(x_test)
    print(classification_report(y_test, predictions))

    model_dir = Path("/opt/ml/model")
    model_dir.mkdir(parents=True, exist_ok=True)
    joblib.dump(model, model_dir / "model.joblib")


if __name__ == "__main__":
    main()

Reproducibility needs more than random_state. I need versioned data, code, dependencies, container image, hyperparameters and environment metadata. Otherwise I may have a model file but no reliable way to explain or rebuild it.

Data is the real foundation

The machine-learning lifecycle depends on trustworthy data. AWS architectures commonly use Amazon S3 as durable object storage. Files may be CSV for interchange, but Parquet is often better for analytical workloads because it is columnar and compressed.

AWS Lake Formation helps govern access to data-lake resources. Amazon Athena queries data in S3 using SQL. Amazon EMR provides managed big-data processing with tools such as Apache Spark. Apache Iceberg adds table semantics over data-lake files, including schema evolution and time travel.

My .NET and SQL background helps here. A data lake is not permission to dump files into random prefixes. It needs ownership, schemas, partitions, cataloguing, retention and quality checks.

Time travel is useful because an ML engineer may need the state of data used by a historical training run. But the design still needs explicit dataset versions. “Latest” is not reproducible.

Data leakage is a major gotcha. A feature may accidentally include information only known after the predicted event. For example, using a post-default collection status to predict default creates impressive evaluation results and a useless production model.

Split data with time in mind. A random split may leak future patterns into training. For time-dependent business behaviour, training on earlier periods and testing on later periods can provide a more honest estimate.

Feature engineering and SageMaker Feature Store

A feature is a model-ready input derived from raw data. loan_to_income_ratio is a feature calculated from requested amount and annual income.

Feature engineering must be consistent between training and inference. If training calculates a ratio one way while the production API rounds differently, the model sees different distributions. This is training-serving skew.

SageMaker Feature Store can manage reusable feature groups. The online store supports low-latency access to current values for real-time inference. The offline store keeps historical values for training and batch work.

My notes for feature design:

  • Give every feature a business definition, owner, type and expected range.
  • Record event time, not only ingestion time.
  • Avoid personal information in names, descriptions and tags.
  • Version breaking changes.
  • Monitor freshness and missing-value rates.
  • Use point-in-time-correct joins so training does not see future values.
A feature store is not automatically necessary for a first experiment. It becomes valuable when teams reuse features, require online/offline consistency or repeatedly rebuild the same transformations.

Processing jobs and data pipelines

SageMaker Processing runs managed jobs for preprocessing, post-processing, evaluation or other data work. Instead of keeping a notebook open, I package the script, define inputs and outputs, and let the service provision temporary compute.

This encourages an important transition:

Notebook exploration
  -> repeatable script
  -> managed processing job
  -> automated pipeline step

Notebooks are excellent for exploration and explanation. They are poor production schedulers. Hidden cell state and out-of-order execution make results difficult to reproduce. Once an experiment matters, move the logic into tested modules and parameterised jobs.

Processing code should validate schemas, ranges, duplicates and null rates before producing output. A pipeline should fail loudly when source data is wrong rather than quietly train a new model on corruption.

Each run should write data-quality metrics and a manifest describing inputs, code version and outputs. This gives operations evidence when a model’s behaviour changes.

Fine-tuning and hyperparameter tuning

Fine-tuning continues training a pretrained model on task-specific examples. It may improve style, format or specialised behaviour. It is not the first answer to every knowledge problem.

If the issue is that a model does not know current company policy, RAG is usually the more natural tool because policy changes independently and needs citations. Fine-tuning is more relevant when I want repeatable task behaviour and have enough high-quality examples.

Hyperparameters are settings chosen before training, such as learning rate, tree depth or number of estimators. Hyperparameter tuning runs multiple training jobs and selects the best result according to a metric.

The risks are cost and false optimisation. A broad search can launch many expensive jobs. The target metric must reflect business value, and the evaluation data must remain honest. Early stopping, bounded ranges and managed budgets are part of the design.

Experiment tracking tools such as MLflow help record runs, parameters, metrics and artefacts. My key learning is that an experiment is useful only if I can compare it and reproduce it.

Choosing a SageMaker inference option

Deployment is where a model becomes an operational dependency. SageMaker AI provides several inference patterns.

Real-time inference uses a persistent managed endpoint for low-latency or sustained traffic. It gives control over instance types and scaling but costs money while provisioned.

Serverless inference suits intermittent or unpredictable traffic. AWS manages infrastructure and charges around use, but cold starts, payload limits and runtime limits must fit the workload.

Asynchronous inference queues requests and is useful for larger payloads or longer processing. The client does not hold an HTTP connection open waiting for completion.

Batch Transform processes a dataset offline without a persistent endpoint. This fits nightly scoring or bulk backfills.

My decision table is:

Need immediate response with steady traffic?       Real-time
Need immediate response with irregular traffic?    Serverless
Long-running or large request with queued result?  Asynchronous
Large offline dataset available upfront?           Batch Transform

The choice should start from latency, volume, payload, availability and cost requirements—not whichever tutorial I completed first.

Safe model deployment

A model deployment changes production behaviour even when the API contract stays the same.

Shadow testing sends a copy of traffic to a candidate model without using its response for the user. This lets me compare performance on realistic requests. I must still handle data privacy and avoid duplicating side effects.

Canary deployment sends a small percentage of live traffic to the new variant, observes health and model metrics, then increases traffic gradually. Blue/green deployment maintains old and new environments so traffic can move between them.

Rollback criteria must be defined before release. Infrastructure metrics such as latency and error rate are not enough. I also need model metrics: prediction distribution, confidence, retrieval quality, groundedness or business outcome depending on the system.

Capture input and output responsibly. Monitoring data can contain sensitive information. Redact or tokenise where possible, restrict access and apply retention policies.

Model drift means production data or relationships change over time. Data drift concerns input distributions; concept drift concerns the relationship between inputs and outcome. A model can remain available and fast while becoming less useful.

Evaluating generative AI systems

Traditional models often have a clear label and metric. Generative output is more difficult because several answers may be acceptable.

I would evaluate dimensions separately:

  • Relevance: does the response address the request?
  • Groundedness: are claims supported by supplied evidence?
  • Correctness: is the result factually right for the task?
  • Completeness: are important points missing?
  • Safety: does it avoid disallowed or harmful output?
  • Format compliance: does it match the required schema?
  • Tool behaviour: did the agent choose and call appropriate tools?
  • Retrieval: were the right passages returned?
Human-reviewed examples remain important. LLM-as-a-judge can scale evaluation by asking another model to score responses against a rubric, but the judge itself can be biased, inconsistent or sensitive to prompt wording. I would calibrate automated scores against human judgements.

Evaluation data should include ordinary, difficult and adversarial cases. For an agent, test tool failure, missing permissions, prompt injection, repeated requests and maximum-step behaviour.

Store evaluation results with model, prompt, retrieval and code versions. Otherwise a score cannot explain which system was tested.

SageMaker Pipelines and LLMOps

SageMaker Pipelines turns lifecycle steps into an automated workflow. A pipeline might:

Process dataset
  -> validate quality
  -> train or fine-tune
  -> evaluate
  -> compare with release threshold
  -> register candidate
  -> require approval
  -> deploy canary
  -> monitor

This resembles CI/CD but with data and model artefacts added. LLMOps extends MLOps to prompts, foundation models, RAG indexes, agent tools and generative evaluations.

A pipeline should be idempotent where possible. Re-running a failed step should not create uncontrolled duplicate endpoints. Names and artefacts need execution identifiers. Cleanup needs to handle partially created resources.

Do not hide approval rules in a notebook. Release thresholds belong in version-controlled pipeline code. Production deployment should require clear evidence and, for consequential systems, human approval.

AWS Lambda can support orchestration tasks, but it should not become a collection of untracked deployment scripts. Infrastructure as code, least-privilege roles and repeatable environments remain important.

Security notes I do not want to forget

Machine learning does not weaken the shared-responsibility model.

Use IAM roles with least privilege. Separate the role used by a person, notebook, processing job, training job, endpoint and agent where their responsibilities differ. Avoid wildcard actions and resources.

Keep workloads in approved Regions. Confirm model and service availability before designing around them. Use VPC connectivity and private endpoints when requirements demand network isolation. Encrypt data in S3, model artefacts and logs with appropriate AWS KMS controls.

Store secrets in AWS Secrets Manager or Systems Manager Parameter Store, not notebooks or environment files committed to source control. Prefer short-lived credentials and roles over long-lived access keys.

Apply S3 Block Public Access. Restrict bucket policies. Separate raw, processed and model artefacts. Enable audit logging through CloudTrail and monitor unusual access.

Agents require special discipline. Every tool should perform its own authorisation. Tool output should be bounded. Destructive actions should require confirmation or an independent workflow. Memory must not mix users or tenants. Do not assume the model will obey a text instruction when IAM can enforce the boundary.

Supply-chain security matters too. Pin dependencies, scan container images and use trusted base images. A notebook installing the latest package from the internet is not a reproducible production build.

Cost notes and cleanup discipline

AWS machine-learning experiments can create persistent costs.

Potential cost sources include SageMaker Studio spaces, notebook compute, training instances, hyperparameter jobs, real-time endpoints, vector storage, S3 data, EMR clusters, Bedrock inference, logging and data transfer.

Before an experiment I should:

  1. Check pricing and Region availability.
  2. Set an AWS Budget and alerts.
  3. Tag resources with project, owner and environment.
  4. Define maximum job and endpoint sizes.
  5. Know the cleanup steps before creation.
After an experiment I should delete endpoints, endpoint configurations, models where appropriate, processing resources, unused indexes and temporary buckets. Stopping a notebook interface may not delete every underlying resource.

Token cost can be controlled with concise context, retrieval limits, caching and choosing a smaller capable model. But optimisation should follow measurement. Compressing context until answers lose evidence is not a saving.

The best cost architecture may use different models for different tasks. A small classifier can route a request; a larger model handles only complex cases. Batch inference may replace a permanently running endpoint for offline work.

Mistakes and gotchas I expect to encounter

The first mistake is beginning with an AWS service instead of a business problem. “We should use SageMaker” is not a requirement. I need an outcome, data, constraints and success measures.

The second is confusing a demonstration with a production system. A notebook that answers five questions does not prove security, evaluation, reliability or cost.

The third is using generative AI where deterministic code is better. Tax calculations, permission checks and critical business rules belong in tested code. A model may explain a rule; it should not invent the rule.

The fourth is treating RAG as guaranteed truth. Retrieval and generation both fail. Citations, evaluation and user feedback are necessary.

The fifth is giving an agent broad permissions. A powerful tool with weak authorisation turns model error or prompt injection into a real-world incident.

The sixth is data leakage between train and test, or between future and past. Excellent evaluation may be an illusion.

The seventh is ignoring class imbalance and using accuracy alone.

The eighth is training-serving skew. The feature code used during training must match production computation.

The ninth is no reproducibility. If data, dependencies, prompts and parameters are not versioned, I cannot defend a result.

The tenth is leaving endpoints running. Cloud convenience makes resource creation easy; cost control requires deliberate cleanup.

My practical learning plan

I do not need to learn every AWS machine-learning service at once.

My first exercise will be a small Bedrock application that calls one foundation model, uses a versioned prompt, validates structured output and records latency and token usage.

My second will add a Knowledge Base over synthetic policy documents. I will create a test set of questions with expected evidence and measure retrieval separately from answer quality.

My third will build a read-only agent with two narrow tools. I will test permissions, tool failures, maximum steps and prompt injection before adding any action.

My fourth will return to traditional ML: prepare a synthetic loan dataset, train an XGBoost binary classifier in SageMaker AI, inspect precision and recall, and deploy it first with Batch Transform.

My fifth will compare real-time, serverless and asynchronous inference against a written workload rather than deploying all three without purpose.

My sixth will place processing, training and evaluation into a SageMaker Pipeline with versioned artefacts and a manual deployment gate.

Throughout, I will keep an architecture record covering identity, data classification, network boundary, cost limit, evaluation method and cleanup plan.

What I understand now

Amazon Bedrock is my managed route into foundation models and generative AI capabilities. SageMaker AI is the broader engineering platform for preparing data, training, tuning, deploying and operating models. AgentCore helps take agents from a local loop toward a governed runtime. S3, Lake Formation, Athena, EMR, Iceberg and Feature Store form parts of the data foundation. Processing jobs and Pipelines turn notebook ideas into repeatable workflows.

The real subject is not how to click through AWS consoles. It is how to operate learning systems responsibly.

The model is one component. Data quality determines what it can learn or retrieve. Evaluation tells me whether it works. Deployment strategy limits release risk. Monitoring tells me when reality has changed. IAM and network controls decide what the system can reach. Cost controls determine whether it is sustainable.

I am still learning, and that is exactly why these notes belong on the website. They record the point where familiar software engineering meets a new discipline. My existing experience is not discarded; it becomes the foundation for asking better questions about reliability, security, architecture and production support.

The sentence I want to remember is this:

Machine learning engineering is not only building a model.
It is building the repeatable, secure and observable system that allows
data and models to create value safely in production.

That is the AWS machine-learning journey I am beginning.

A final note to my future self

When I return to these notes, I do not want to measure progress by how many AWS service names I can remember. I want to measure it by whether I can take a real problem and ask the right questions. Is this actually a prediction problem, a retrieval problem, or simply ordinary application logic? What evidence would show that the result is useful? Which data may be used, who owns it, and how will I detect when it changes? What happens when the model is uncertain, unavailable, slow, expensive or confidently wrong?

That is a reassuring way to learn this subject. I do not need to become a research scientist before I can contribute. I can bring the habits I already value as a senior developer: make assumptions visible, keep boundaries clear, test failure paths, prefer small releases, observe production behaviour and never confuse a successful demo with a dependable system. The AWS services give me managed building blocks, but they do not make those engineering decisions for me.

My next practical milestone is intentionally modest: build one small retrieval application over documents I understand, create a short evaluation set by hand, record latency and cost, and review every poor answer. Only then will I add an agent or another model. If I cannot explain the simple version, adding autonomy will only hide the gaps in my understanding.

Mentoring session: build one prediction system end to end

The service map becomes useful only when it helps me make decisions. I will therefore mentor a junior developer through one illustrative project: predict whether a support case is likely to breach its response-time target. This is not a claim about a system I have deployed, and the sample data is synthetic. It is a teaching case because the outcome is understandable, the cost of mistakes can be discussed, and the same lifecycle applies to many tabular classification problems.

The imaginary support team receives cases with a priority, product area, customer tier, creation time and short description. The desired outcome is not “use machine learning.” The outcome is earlier intervention on cases at risk of breaching their service target. A prediction is useful only if it reaches an operator early enough to change the result.

Junior: Can we define success as 90% accuracy?
>
Senior: Not yet. First tell me what decision the prediction changes. If only 5% of cases breach, a model that always says “safe” is 95% accurate and completely useless. We need a metric connected to the intervention.
That conversation produces a one-page problem statement:
  • The unit of prediction is one support case.
  • The prediction time is fifteen minutes after creation.
  • The label is whether the first-response target was breached.
  • The user is the duty support lead.
  • The action is reassignment or escalation, not an automatic penalty against an employee.
  • Missing a genuinely risky case is more costly than reviewing an extra false alarm, but excessive alerts will be ignored.
  • The initial system is advisory and can be disabled without interrupting case handling.
This statement prevents a common failure: creating a technically impressive model whose output arrives too late or has no owner. It also exposes an ethical boundary. Features must describe the case and operational context; we should not smuggle protected or inappropriate personal characteristics into the model.

Define the contract before touching a notebook

I would write a small decision contract beside the code:

prediction_name: response_target_breach_risk
entity: support_case
prediction_time: fifteen_minutes_after_creation
label: breached_first_response_target
positive_action: queue_for_duty_lead_review
decision_owner: support_operations
maximum_decision_latency_ms: 800
fallback: continue_normal_priority_rules
minimum_recall: 0.80
minimum_precision: 0.35
protected_use: advisory_only

The numbers are hypotheses, not universal recommendations. Operations, product, risk and engineering must agree them. The key is that they exist before optimisation begins. A model threshold can then be selected against a real operating constraint: how many cases can the duty lead inspect per hour?

Junior: Why set both recall and precision?
>
Senior: Recall asks how many actual breaches we caught. Precision asks how many alerts were genuinely risky. Optimising recall alone can alert on everything. Optimising precision alone may surface only a tiny handful. The business needs a workable balance.
For binary classification:
precision = true positives / (true positives + false positives)
recall    = true positives / (true positives + false negatives)

A probability is not itself a decision. The threshold converts a score into an alert. We should retain the score, threshold, model version and reason information so the later outcome can be audited.

Data design: ask what was knowable at prediction time

The dataset is not “all columns we can find.” Each training row must recreate what the application knew fifteen minutes after case creation. Suppose a case ultimately took twelve hours to resolve. Using final resolution category, final assignee or the number of later messages would leak the future into training.

I would begin with a feature availability table:

FeatureSourceAvailable at prediction time?Risk
Initial priorityCase APIYesUser-entered inconsistency
Product areaCase APIYesTaxonomy changes
Customer support tierAccount APIYesRequires authorised join
Queue depthMetrics snapshotYes, if timestampedHistorical reconstruction
Description lengthInitial case textYesWeak proxy only
Final assigneeCase historyNoFuture leakage
Resolution codeCase historyNoDirect leakage
Messages after 15 minutesTimelineNoFuture leakage
The table is a lightweight defence against accidental cheating. It also identifies ownership, privacy and freshness dependencies.
Junior: The final assignee strongly predicts a breach. Why not use it?
>
Senior: Because it did not exist when we promise to predict. Offline evaluation would be excellent, production results would collapse, and we would have trained on the answer’s consequences.
Time makes the split important. A random split mixes old and new cases and can place near-duplicates from the same incident on both sides. For this system I would train on an earlier window, validate on a later window and keep the newest untouched period as a final test. That better simulates deployment into the future.
import pandas as pd

cases = pd.read_parquet("cases_as_known_at_prediction_time.parquet")
cases["created_at"] = pd.to_datetime(cases["created_at"], utc=True)
cases = cases.sort_values("created_at")

train = cases[cases.created_at < "2025-07-01"]
validation = cases[
    (cases.created_at >= "2025-07-01")
    & (cases.created_at < "2025-10-01")
]
test = cases[cases.created_at >= "2025-10-01"]

assert train.created_at.max() < validation.created_at.min()
assert validation.created_at.max() < test.created_at.min()

The dates merely demonstrate the shape. Real windows must cover seasonality and operational changes. If service targets changed in September, I would mark that event and decide whether older labels still represent the current problem.

Data validation belongs before feature generation. At minimum I want checks for schema, identifiers, duplicates, label values, timestamp ordering, null rates, allowed categories and plausible numeric ranges.

REQUIRED = {
    "case_id", "created_at", "priority", "product_area",
    "support_tier", "queue_depth", "breached_target"
}

missing = REQUIRED - set(cases.columns)
if missing:
    raise ValueError(f"Missing required columns: {sorted(missing)}")

if cases.case_id.duplicated().any():
    raise ValueError("case_id must be unique")

if not set(cases.breached_target.dropna().unique()) <= {0, 1}:
    raise ValueError("breached_target must be binary")

if (cases.queue_depth < 0).any():
    raise ValueError("queue_depth cannot be negative")

In production I would use a data-validation library or a managed processing step, but explicit examples make the invariants visible. A failed validation should stop training. Silently coercing an unknown priority to zero may keep a job green while damaging the model.

Features without training-serving skew

Feature engineering turns raw facts into model inputs. Examples might include hour of day, open-case count, recent arrival rate and whether the product has an active incident. The dangerous version implements these transformations once in a notebook and again in an API. Small differences in time zones, null handling or category mapping create training-serving skew.

I prefer one versioned transformation module used by batch preparation and online inference, with fixture tests:

from dataclasses import dataclass
from datetime import datetime, timezone

@dataclass(frozen=True)
class CaseSnapshot:
    priority: str
    product_area: str
    support_tier: str
    queue_depth: int
    created_at: datetime

def build_features(case: CaseSnapshot) -> dict[str, float | str]:
    created = case.created_at.astimezone(timezone.utc)
    return {
        "priority": case.priority.strip().lower(),
        "product_area": case.product_area.strip().lower(),
        "support_tier": case.support_tier.strip().lower(),
        "queue_depth": max(case.queue_depth, 0),
        "created_hour_utc": created.hour,
        "created_weekend": int(created.weekday() >= 5),
    }

The code should reject an impossible timestamp rather than invent one. Categories need an explicit unknown policy. Tests should cover daylight-saving boundaries, null fields, unseen products and negative values.

SageMaker Feature Store can manage reusable features. AWS documents an online store for the latest low-latency values and an offline, historical store for exploration, training and batch inference. Using both can help align training and serving, but it is not automatically necessary. A small batch-only model may be clearer with versioned Parquet in S3 and tested transformation code. Add a feature store when reuse, freshness, history and governance justify its operational surface.

Point-in-time correctness remains essential. When building the training row for 10:15, a join must select the latest queue snapshot at or before 10:15—not today’s latest value. An ordinary join on case identifier can quietly introduce future facts.

Establish a baseline before a sophisticated model

The first competitor is the existing rule, not another machine-learning algorithm. Perhaps priority-one cases and cases created during a known incident are already escalated. Record that rule’s recall, precision, alert volume and operational cost.

Then train a simple baseline such as logistic regression. A baseline tests whether the data and evaluation path are sensible and gives a readable comparison. XGBoost may perform better on tabular data, but complexity has to earn its place.

from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, precision_recall_curve
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

categorical = ["priority", "product_area", "support_tier"]
numeric = ["queue_depth", "created_hour_utc", "created_weekend"]

preprocess = ColumnTransformer([
    ("category", OneHotEncoder(handle_unknown="ignore"), categorical),
    ("number", StandardScaler(), numeric),
])

model = Pipeline([
    ("features", preprocess),
    ("classifier", LogisticRegression(class_weight="balanced", max_iter=1000)),
])

model.fit(train[categorical + numeric], train["breached_target"])
probability = model.predict_proba(validation[categorical + numeric])[:, 1]

I would graph the precision-recall curve and calculate outcomes at candidate thresholds. ROC AUC is useful for ranking across thresholds, but it can look reassuring on an imbalanced problem. The duty lead experiences alerts, so precision, recall, alert count and cases missed at the chosen threshold are more tangible.

def threshold_report(y_true, probability, threshold):
    predicted = (probability >= threshold).astype(int)
    return classification_report(y_true, predicted, output_dict=True)

for threshold in (0.25, 0.40, 0.55, 0.70):
    report = threshold_report(validation.breached_target, probability, threshold)
    positive = report["1"]
    print(threshold, positive["precision"], positive["recall"])

Do not choose the threshold on the test set. Use training for fitting, validation for model and threshold choices, and the held-back test once for the release estimate. Repeatedly checking the test set turns it into another validation set.

Calibration asks whether cases scored around 0.7 actually breach about 70% of the time. A ranking can be useful without perfect calibration, but calibrated probabilities matter when the score drives staffing or expected-cost calculations. Inspect calibration plots and consider calibration techniques fitted only on appropriate held-out data.

Evaluation should also be sliced. Overall performance can hide failure for a product, tier, time band or new category. Slices need enough observations to support a conclusion; tiny groups should not produce confident claims. I would publish a model card containing intended use, excluded use, data period, features, metrics, slice results, threshold, limitations and approval owner.

Junior: The new model beats the baseline by two percentage points. Can we deploy?
>
Senior: Tell me the confidence interval, alert volume, slice behaviour, latency, failure mode and operational benefit. A decimal improvement is not a release decision by itself.

Make the SageMaker training job reproducible

A managed training job should receive immutable inputs and emit an immutable model artefact plus metrics. It should not depend on notebook state or an engineer’s local files.

A practical repository boundary might be:

ml/
  processing/
    validate.py
    transform.py
  training/
    train.py
    requirements.lock
  evaluation/
    evaluate.py
  inference/
    serve.py
  tests/
pipeline/
  definition.py
infrastructure/
  model-platform.bicep-or-cdk

Yes, an AWS project would normally use CloudFormation, CDK or Terraform rather than Bicep; the point is to version infrastructure. The training script should log the source data version, Git commit, container image digest, dependency lock, parameters, random seed and metric definitions. A seed helps repeat a run but does not guarantee bit-for-bit identity across every library and hardware implementation, so record the environment too.

The model artefact is untrusted input to the serving environment. Limit who can write its S3 location, encrypt it, scan containers, and avoid unsafe deserialisation formats where possible. A pipeline role should have only the permissions required for its steps; the endpoint should not inherit broad training permissions.

SageMaker Pipelines can connect processing, training, evaluation and registration. The useful part is not the diagram in the console. It is an executable, version-controlled release argument:

validated data + pinned code
  -> repeatable training
  -> independently calculated evaluation
  -> explicit quality condition
  -> registered candidate
  -> approval
  -> controlled deployment

Model Registry provides versions and lifecycle status. Registering a model does not prove it is safe; it stores the candidate and its evidence so a release process can make a decision. I would require evaluation artefacts, container digest, inference schema, owner and rollback reference before approval.

Design the inference boundary as an ordinary production API

The endpoint contract needs the same care as any other service. It should validate input, impose size limits, authenticate callers, set timeouts and emit correlation identifiers. It should not return a raw library object.

{
  "caseId": "synthetic-1042",
  "snapshotAt": "2026-07-30T10:15:00Z",
  "features": {
    "priority": "high",
    "productArea": "billing",
    "supportTier": "standard",
    "queueDepth": 48
  }
}
{
  "caseId": "synthetic-1042",
  "riskScore": 0.73,
  "decision": "review",
  "threshold": 0.55,
  "modelVersion": "breach-risk/17",
  "predictionId": "01J..."
}

The consumer should have a fallback. If prediction times out, normal priority rules continue; the case is not lost. Retrying must be bounded and jittered. If a retry can duplicate an event, use an idempotency key. Load tests should cover concurrency, payload distribution and cold behaviour, not a single ideal request.

For a first learning release I would prefer Batch Transform over a live endpoint. A scheduled job scores current open cases and writes results for review. This reduces the blast radius and teaches the artefact and evaluation path. If the business later proves that minute-level decisions create value, a real-time endpoint becomes justified.

Real-time, serverless, asynchronous and batch options are workload choices. Persistent real-time capacity may be right for predictable low latency. Serverless can suit intermittent traffic but requires testing cold starts and supported limits. Asynchronous inference fits queued longer requests. Batch Transform fits bulk work with no persistent endpoint. Pricing and regional support must be checked at design time because they change.

Release progressively and make rollback mechanical

The first production phase is shadow mode: calculate predictions and record them without changing the queue. Compare the predictions with later labels and with the existing rule. Confirm latency, schema compatibility, alert volume and slice behaviour. Shadow mode is not risk-free—production data is still processed and must follow privacy and retention rules.

Next, allow a small group of trained users to see advisory alerts. Record whether they accepted, rejected or ignored a suggestion, but do not treat every click as a perfect label. Human behaviour can be biased by the model’s presentation.

For endpoint updates, AWS documents blue/green deployment guardrails with canary and linear traffic shifting plus CloudWatch-alarm-based automatic rollback. A canary sends only part of traffic to the new fleet during a baking period before a full shift. That is valuable for technical regressions, but the alarm set must cover what can fail.

I would define release checks like this:

Immediate rollback:
  5xx rate > 1% for 5 minutes
  p95 model latency > 600 ms for 10 minutes
  invalid response schema > 0
  missing required feature > 0.5%

Pause and investigate:
  alert rate outside 5%-20% expected band
  score distribution materially different from shadow baseline
  a monitored slice falls below agreed recall

Continue only when:
  bake period completes
  no rollback alarm fires
  operational owner confirms alert workflow is usable

These values are illustrative. The lesson is to choose them before deployment. CloudWatch alarms can automate infrastructure rollback. Delayed label quality may require a human pause or a later rollback because the true outcome is not immediately known.

Rollback means more than “deploy yesterday’s container.” Preserve the previous model, endpoint configuration, compatible feature contract and threshold. If a schema migration is one-way, rollback may fail even though the model artefact exists. Compatibility tests should invoke both current and candidate versions with representative payloads.

Junior: If the canary has no errors, is the model good?
>
Senior: It is healthy enough to serve. That proves transport and runtime behaviour, not prediction value. Model quality often needs later ground truth, so release confidence accumulates in stages.

Monitor four different systems, not one metric

Production monitoring should separate four layers:

  1. Service health: requests, errors, latency, throttling, saturation and dependency failures.
  2. Data health: missing fields, unknown categories, ranges, freshness and input distribution.
  3. Model behaviour: score distribution, alert rate, calibration, slice results and eventual precision/recall.
  4. Business outcome: breaches prevented, review workload, time to intervention and unintended consequences.
An endpoint can be technically perfect while the upstream API begins sending unknown for every product. Data checks catch that sooner than waiting weeks for labels. Conversely, input distributions can shift while quality remains acceptable; drift is a signal to investigate, not proof that retraining is required.

The delayed-label join deserves its own pipeline. A prediction record needs entity ID, prediction time, features or a governed feature reference, score, threshold, decision and versions. When the outcome becomes final, join it using the definition fixed at prediction time. Late or corrected labels must be handled explicitly.

prediction_id
case_id
predicted_at
model_version
feature_schema_version
score
threshold
decision
label_available_at
observed_label

SageMaker Model Monitor historically provides scheduled monitoring for data quality, model quality, bias drift and feature-attribution drift. However, the AWS documentation now states that new customer access closes on 30 July 2026 and existing customers can continue to use it. Therefore I would first check account eligibility and current AWS guidance. The durable architecture is the monitoring contract above; it can be implemented with eligible SageMaker capabilities or with processing jobs, CloudWatch, EventBridge, S3, Athena and organisation-approved observability tooling.

This is a useful architectural lesson: never make the safety requirement synonymous with one product. “Detect schema changes within fifteen minutes” is a requirement. “Use Product X” is one implementation.

Incident clinic: the model suddenly alerts on nearly every case

Imagine the alert rate rises from 12% to 78% after a routine application release. The endpoint is green: latency and errors are normal. This is exactly why application health and model health need separate dashboards.

I would respond in this order:

  1. Disable or bypass the advisory alert if it is overwhelming operators.
  2. Preserve request samples, correlation IDs and version metadata within privacy rules.
  3. Compare model, container, threshold and feature-schema versions with the last healthy period.
  4. Inspect null, unknown-category and range metrics by feature.
  5. Compare upstream deployment times and data-contract changes.
  6. Replay a small governed sample through the previous and current paths.
  7. Roll back the responsible component or restore the rules fallback.
  8. Only then decide whether retraining is relevant.
Suppose queue_depth changed from a count to a percentage but retained the same numeric type. Schema validation would pass; range and distribution checks would not. Retraining on the malformed values would institutionalise the bug. The correction is a data contract, a semantic version change and a consumer compatibility test.
Junior: The distribution moved, so should the pipeline automatically retrain?
>
Senior: No. First establish why it moved. A product launch, a measurement bug and a genuine behavioural change require different responses. Automatic retraining can turn upstream corruption into a newly approved model.
The post-incident work would add an invariant for queue-depth units, contract tests between the case API and feature pipeline, an alert-rate circuit breaker, a runbook link in the alarm and a deployment event overlay on the dashboard. The postmortem should improve the system, not blame the person who happened to release it.

Cost and capacity as part of model design

Cost is not just the hourly price of a training instance. Include processing, tuning trials, endpoint uptime, autoscaling headroom, Feature Store, S3, logs, data transfer, monitoring jobs and engineering time.

For each inference option I would estimate:

monthly requests
x average processing duration
x provisioned or consumed capacity price
+ idle capacity
+ storage and monitoring
+ expected experiment and retraining spend

Then validate the estimate with tagged resources and billing data. Put an expiry tag on experiments and an owner on persistent endpoints. Budgets alert after spend begins; quotas, maximum instance parameters and deployment policy can prevent some mistakes earlier.

A larger model is not automatically better value. If the logistic baseline meets the operational threshold, an expensive ensemble may not justify harder explanations and higher latency. If scoring once every fifteen minutes is sufficient, a batch job may beat a continuously provisioned endpoint. Architecture is economics joined to reliability.

A review checklist I can use with a junior developer

Before approving a pull request for this project, I would ask:

  • Can the author state the decision, user, action and fallback without naming an AWS service?
  • Is every feature available at prediction time, with evidence?
  • Are train, validation and test separated in a way that represents future use?
  • Are transformations shared or contract-tested between training and serving?
  • Is the existing rule measured as a baseline?
  • Does the report include precision, recall, threshold, alert volume and meaningful slices?
  • Can another engineer reproduce the run from immutable data, code and configuration?
  • Does the model record have intended use, limitations, ownership and approval evidence?
  • Is the inference request validated and authenticated?
  • Are timeouts, retries, idempotency and fallback behaviour tested?
  • Are deployment alarms and rollback artefacts ready before traffic shifts?
  • Can monitoring distinguish service, data, model and business failures?
  • Is delayed ground truth joined reliably?
  • Are sensitive inputs minimised, encrypted and retained only as required?
  • Are cost limits, tags and cleanup steps tested?
The review should invite explanation rather than reward memorised terminology.
Junior: I cannot answer the slice-performance question yet. Does that block every experiment?
>
Senior: It blocks a claim that the model is ready for consequential use. It does not block learning. Write down the evidence gap, keep the system in a non-decisioning environment, and design the next experiment to close it.

Exercises for the reader I am mentoring

Exercise one: catch leakage

Create twenty synthetic cases with timestamps. Add three tempting columns that occur after prediction time. Write a test that rejects them from the feature list. Then build a time-based split and assert its ordering. Explain why a random split gives a different risk.

Exercise two: choose a threshold from workload

Generate validation probabilities and labels. Calculate precision, recall, false positives and false negatives at five thresholds. Assume the team can review thirty alerts per day. Recommend a threshold and write down the cost assumption behind it. Change that capacity to ten and see why the “best” threshold moves.

Exercise three: package a repeatable job

Move notebook transformations into a module. Pin dependencies, add fixture tests and make a command accept input and output S3 URIs as parameters. Run it twice against the same immutable data and compare artefacts and metrics. Record anything that prevents reproduction.

Exercise four: practise rollback

Deploy only synthetic traffic to two model versions. Create an alarm on an intentionally failing response metric and observe the controlled rollback path. Confirm that the consumer fallback works while the endpoint changes. Delete the resources afterwards and verify the billing/resource inventory rather than assuming cleanup succeeded.

Exercise five: diagnose drift without retraining

Alter one upstream feature distribution. Produce a dashboard showing service health, data health and score distribution. Write three competing explanations and the evidence needed to distinguish them. Do not retrain. This exercise teaches investigation before automation.

How I would run the model-readiness meeting

A useful readiness meeting is not a slide presentation where the model author demonstrates their favourite metrics. It is a structured challenge involving the decision owner, data owner, application engineer, operations representative and security or risk specialist appropriate to the system.

I would send a short evidence pack in advance. It would contain the problem contract, data lineage, feature availability table, baseline comparison, evaluation report, model card, architecture, threat assessment, cost estimate, deployment plan and rollback runbook. Links should point to immutable run artefacts rather than screenshots copied from a notebook.

The meeting begins with the decision owner, not the data scientist:

Senior: What will you do differently when this model says “review”?
>
Operations lead: The duty lead will inspect the case within ten minutes and can reassign it or leave it unchanged.
>
Senior: What happens when predictions are unavailable?
>
Operations lead: Existing priority and incident rules continue. No case waits for the model.
That exchange proves the model is an advisory enhancement rather than a hidden single point of failure. Next, the data owner explains label construction and time boundaries. The model author then shows the baseline and candidate at the proposed threshold, including counts rather than only ratios:
Held-back period:       12,000 cases
Actual breaches:           600
Alerts produced:         1,420
Breaches caught:           486
Breaches missed:           114
False alerts:              934
Recall:                  81.0%
Precision:               34.2%

Those figures are illustrative, but counts make consequences visible. The team can ask whether 1,420 reviews fit available capacity and whether 114 missed cases are acceptable. Averages should be followed by time and product slices, uncertainty, known exclusions and the performance of the current rule on the same held-back period.

The application engineer demonstrates the request contract, timeout, fallback and compatibility tests. Operations walks through dashboards and an alarm. Security confirms data classification, role boundaries, encryption, network path, audit trail and retention. Finance or the platform owner reviews a realistic load estimate and cost ceiling. Finally, the chair records one of three outcomes: approved for a named limited phase, changes required with owners, or rejected with reasons.

Approval should expire when an assumption changes. A new label definition, feature schema, population, model family or decision use may require a new review. Changing an endpoint instance size probably needs technical validation but not a complete ethical review. Define the distinction in release policy rather than arguing during an incident.

Feedback loops, bias and human oversight

Once operators see a score, their actions change the data. If a high-risk case is escalated and therefore does not breach, the observed label is zero even though the intervention may have prevented the breach. Naively retraining on outcomes can teach the model that escalated high-risk cases were safe. This is an intervention feedback loop.

The prediction store should therefore record the action taken and its timing. Evaluating causal benefit is harder than ordinary classification. A controlled trial may be appropriate, but withholding an intervention can be unacceptable in higher-risk settings. I would involve domain and experimentation specialists instead of claiming that historical correlation proves impact.

Human review is also not automatically fair. Operators can over-trust a confident score, ignore low scores, or apply suggestions differently across customer groups. The interface should show that the score is advisory, offer relevant explanation without pretending it is causal, and make disagreement easy to record. Training should explain known limitations and the fallback procedure.

Protected characteristics require careful governance. Simply removing a protected column does not remove proxies such as location, language or account attributes. Slice evaluation can reveal disparities, but group metrics need context, lawful purpose and adequate sample sizes. The correct fairness definition depends on the decision and potential harm; there is no single metric that certifies fairness.

Junior: Should we include the protected attribute so the model can be tested for bias?
>
Senior: Prediction use and evaluation use are different questions. An authorised, tightly governed evaluation dataset may need attributes that the serving model must not consume. Work with legal, privacy and risk owners; do not improvise a policy in code.
The model card should state whose outcomes were represented in training, whose were not, which slices were evaluated, and what the model must never decide. Audit access to evaluation attributes. Retain only what policy and law permit. If adequate evaluation is impossible, record that uncertainty and reduce the use or impact of the model.

Debugging workshop: offline score is good, online quality is poor

This failure is common enough to deserve a repeatable investigation. I would resist immediately changing algorithms and create a comparison table for identical entities:

case_id
offline_feature_vector
online_feature_vector
offline_score
online_score
model_version
transformation_version
prediction_timestamp

First confirm that the same model bytes and threshold are used. Next compare feature values field by field. Differences often come from time zones, category casing, defaults, late data, join direction, rounding or online freshness. Then replay captured, privacy-approved requests in a non-production environment. A reproducible mismatch is far easier to fix than an aggregate dashboard anomaly.

If features match, inspect preprocessing library versions and inference modes. A text tokenizer, image resize, missing-value treatment or column order can change the input without changing the JSON schema. Package preprocessing with the model where practical and validate a signature over ordered feature names and types.

If offline and online scores match but observed quality differs, challenge the evaluation population and label pipeline. Production may contain new products, a marketing campaign, abuse traffic or a changed service target. Ground truth may arrive late or be joined incorrectly. Compare only cohorts whose labels are mature; otherwise the newest period may appear artificially successful because breaches have not had time to occur.

If the metric itself is correct, inspect the intervention. Perhaps alerts reach an unattended queue, arrive after the useful moment, or contain too little context for action. That is not a model-training failure. It is a product workflow failure, and retraining will not repair it.

The investigation order is therefore:

artefact identity
  -> feature parity
  -> preprocessing parity
  -> population and label validity
  -> threshold and decision logic
  -> operational intervention

This order protects us from spending a week tuning XGBoost when an enum changed from P1 to priority_one.

Production readiness as executable evidence

Checklists are useful, but automated evidence is better. I would make the pipeline publish a release manifest:

{
  "modelPackage": "breach-risk/17",
  "gitCommit": "example-immutable-sha",
  "trainingData": "s3://example/versioned/manifest.json",
  "featureSchema": "3.1.0",
  "containerDigest": "sha256:example",
  "decisionThreshold": 0.55,
  "evaluationReport": "s3://example/evaluation/17.json",
  "approvedFor": "shadow",
  "approvedBy": "recorded-workflow-identity",
  "rollbackModelPackage": "breach-risk/16"
}

The values here are placeholders. In a real system, pipeline code produces them and deployment verifies them. The deployer should reject an artefact lacking its evaluation, approval or compatible schema. This turns governance from a document someone might forget into a release condition.

Automated tests can assert that every required metric exists, the chosen threshold was evaluated, no prohibited feature appears, the container passes vulnerability policy, inference examples conform to schema and rollback points to an available artefact. An integration test invokes the candidate in a temporary environment. A smoke test after deployment checks a small set of non-sensitive fixtures. Synthetic canaries continue checking the path without placing customer records in source control.

Not every check should be automated. Whether the outcome is valuable, whether a slice disparity is acceptable, and whether users understand the intervention need accountable human judgement. Good MLOps automates repeatable evidence and leaves value judgements visible to named people.

A thirty-day learning sequence

To make these notes actionable, I would give a junior developer four weekly outcomes rather than thirty unrelated tutorials.

In week one, define the synthetic support-case problem, generate data, document feature times and implement validation. Deliver a versioned dataset manifest and leakage tests. No SageMaker endpoint is needed.

In week two, build the rule baseline and logistic model, then compare thresholds using counts, curves and slices. Deliver a model card and explain the result in a review. The ability to say “this model is not useful” is a successful outcome.

In week three, move processing and training into parameterised SageMaker jobs and connect them with a small Pipeline. Register a candidate only when the evaluation condition passes. Tear down experimental resources and reconcile them against the cost inventory.

In week four, score synthetic cases in batch, add the prediction record and delayed-label join, and practise a failed deployment and rollback. Build separate service, data and model dashboards. Finish by running the incident clinic without notes.

Each week ends with three questions: What evidence did we create? Which assumption remains weakest? What would make this unsafe in production? That rhythm develops engineering judgement more effectively than collecting service badges.

The mature answer to “How do I learn machine learning engineering on AWS?” is not a longer list of services. It is repeated practice with an evidence chain.

decision and owner
  -> point-in-time-correct data
  -> reproducible features and training
  -> honest evaluation
  -> governed candidate
  -> reversible release
  -> layered monitoring
  -> feedback and accountable improvement

AWS supplies strong managed components for that chain: S3 for governed artefacts, processing and training jobs for repeatable compute, Feature Store where shared online and historical features are justified, Pipelines for workflow, Model Registry for candidates, inference options for different traffic shapes, and deployment guardrails for safer endpoint updates. None of them chooses the right label, prevents a misleading metric or defines acceptable harm for us.

That is the point I would want a junior developer to carry into the next project. Start with the decision. Preserve time. Establish a baseline. Make every artefact traceable. Treat a model as a fallible dependency. Release it in a way that can be reversed. Monitor whether it remains useful, not merely whether its process is alive.

References I am keeping with these notes

Applied In

The thinking in this article has been applied throughout my enterprise portfolio, where architecture, workflows, permissions, notifications, reporting and modular design are all built around real business operations rather than isolated technical features.

View Continuous Learning →

Use this journal entry for recall practice

Compare your explanation with the questions and working answers in my Practice Room.

Practise AWS machine learning and AI architecture questions →
Afzal Ahmed

Faz Ahmed

Senior Full Stack Engineer & Technical Lead

A hands-on engineer with 15+ years in commercial software. I publish what I am studying, revising and testing so visitors can see both established experience and learning still in progress.

How would you approach this problem? I'd love to hear your thoughts or continue the discussion.

Connect on LinkedIn →