Building an Agent Skill Hub: From Skill Development to Automated Multilingual Documentation Deployment on GitHub Pages

image-20260322225856161

image-20260322225856161

Reference links:

This article documents how I built a skill description specification from scratch and created a GitHub Pages documentation site that supports both Chinese and English, drawing inspiration from minimalist aesthetics, while developing the Agent Skill Hub (2026 Skill Library).

Background

With the popularity of AI Agents (such as OpenClaw or Gemini CLI), we found that “how to quickly understand and execute specific tasks for the Agent” has become key. Instead of writing long prompts every time, it’s better to package common operations into standardized Skills.

To facilitate community communication and Agent reading, I created agent-skill-hub. But code alone is not enough; we also need a decent “facade” – a document website that is both aesthetically pleasing and has technical details.

🛠️ Step 1: Standardize Skill Descriptions (SKILL.md)

In agent-skill-hub, each skill (such as gcp-helper or n8n-executor) has a SKILL.md. The structure of this file is crucial because it’s not just for humans to read, but also for LLMs to read:

  • Name & Description: Let the Agent know what this is.
  • When to Use: Define trigger scenarios.
  • Core Pattern: Provide standard instruction examples.
  • Common Mistakes: Reduce errors caused by Agent hallucinations.

🎨 Step 2: Design Style — Tribute to Minimalist Aesthetics

When designing the web pages under the docs directory, I referenced the style of whisperASR. That design of a dark background with bright accent colors (Teal) is very in line with the aesthetics of modern developers:

Visual Element Highlights:

  1. Gradient Title: Use linear-gradient to create a high-end texture.
  2. Teal Accent Color: Use #14b8a6 as the highlight color for key buttons and titles.
  3. Card-style Layout: Clearly present the icons and introductions of each skill, with good responsive design.

🌐 Step 3: Multilingual Support and Automatic Switching

To make it available to developers worldwide, I adopted a directory-structured language management method:

docs/
├── index.html (Language detection and redirection)
├── en/ (English version)
│ └── skills/
└── zh/ (Traditional Chinese version)
    └── skills/

I added a simple JavaScript snippet to the root directory’s index.html, which automatically redirects to the correct language based on the user’s browser settings:

const lang = navigator.language || navigator.userLanguage;
if (lang.startsWith('zh')) {
    window.location.href = './zh/index.html';
} else {
    window.location.href = './en/index.html';
}

🚀 Step 4: GitHub Pages Deployment Process

In 2026, the most recommended deployment method is to put the content in the docs/ directory of the main branch, which can keep the main branch clean while keeping development and documentation synchronized.

1. Prepare the Directory Structure

Create all the necessary directories at once using the command:

mkdir -p docs/en/skills docs/zh/skills docs/assets/css

2. Git Commit and Push

After completing HTML/CSS development, execute the standard Git process:

git add docs/
git commit -m "docs: add GitHub Pages documentation in English and Chinese"
git push origin main

3. Enable GitHub Pages Settings

  1. Go to Settings > Pages in the GitHub repository.
  2. Under Build and deployment, in Branch, select the main branch and the /docs folder.
  3. Click Save, and the website will be online in a few minutes.

image-20260322225932252

🛠️ Common Pitfalls and Troubleshooting

❓ Why can’t the webpage style (CSS) be loaded?

Reason: In HTML files under subdirectories (such as en/skills/), the referenced paths must correctly use relative paths. Correction:

<!-- In the home page index.html -->
<link rel="stylesheet" href="../assets/css/style.css">
<!-- In the skill detail page -->
<link rel="stylesheet" href="../../assets/css/style.css">

❓ How to ensure that the Agent can correctly read the document?

We have retained a large number of semantic tags (article, h2, pre, code) in the HTML, so that the Agent can more accurately capture the core logic when performing RAG (Retrieval-Augmented Generation) or directly reading the webpage.

🏁 Conclusion

Through this development, I have realized the importance of “documentation as product”. A good AI skill library, in addition to powerful program logic, also needs a clear, intuitive, and multilingual-friendly navigation system.

If you also want to create a professional facade for your AI project, you might as well refer to the docs/ structure layout. Happy Coding! 🦞

Security Declaration for AI Agents: Deep Dive into A2AS (Agent-to-Agent Security) Certification Mechanism

A2AS-CERTIFIED

A2AS-CERTIFIED

Reference links:

This article documents an interesting Pull Request I received while maintaining linebot-adk (LINE Bot Agent Development Kit): adding the A2AS security certificate to the project. This is not just a YAML file, but a significant milestone for AI Agents to move towards “industrial-grade security” in 2026.

Google Chrome 2026-03-26 22.45.44

Background

When we develop Agents like linebot-adk that have Tool Use (Function Calling) capabilities, the biggest concern for users is often: “Will this Agent issue commands without my permission?” or “What data can it access?”.

Traditionally, we could only write explanations in README.md, but that’s for humans to read, not for system verification. This is why A2AS (Agent-to-Agent Security) emerged – it’s hailed as the “HTTPS of the AI world”.

🛠️ Step 1: Understanding the BASIC Model of A2AS

A2AS is not just a name; it has a complete BASIC security model behind it, designed to solve the trust issue between AI Agents:

  • (B)ehavior Certificates: Declarative certificates that clearly define the behavior boundaries of the Agent.
  • (A)uthenticated Prompts: Ensures that the source of prompts is trustworthy and traceable.
  • (S)ecurity Boundaries: Uses structured tags (such as <a2as:user>) to isolate untrusted input.
  • (I)n-Context Defenses: Embeds defense logic in prompts to reject malicious injections.
  • (C)odified Policies: Writes business rules into code and enforces them during inference.

🎨 Step 2: Deconstructing a2as.yaml – The Agent’s ID Card

In PR #1 received by linebot-adk, the most core change was the addition of a2as.yaml. This file is like the Agent’s “digital signature”, making the code logic explicit:

manifest:
  subject:
    name: kkdai/linebot-adk
    scope: [main.py, multi_tool_agent/agent.py]
  issued:
    by: A2AS.org
    url: https://a2as.org/certified/agents/kkdai/linebot-adk

agents:
  root_agent:
    type: instance
    models: [gemini-2.5-flash]
    tools: [get_weather, get_current_time]

Why is this important?

This certificate is directly linked to the content of our main.py. When the certificate declares tools: [get_weather, get_current_time], it means this is a limited-authorization Agent. If it tries to execute delete_database, the security monitoring system can immediately detect that it is outside the certificate scope.

🌐 Step 3: Combining Code Logic

In linebot-adk, we used Google’s ADK (Agent Development Kit) to build the Agent. The A2AS certificate can accurately map our program architecture:

1. Tool Declaration and Implementation

In multi_tool_agent/agent.py, we defined two tools:

def get_weather(city: str) -> dict:
    # Implement the logic to get the weather
    ...

def get_current_time(city: str) -> dict:
    # Implement the logic to get the time
    ...

The A2AS certificate will register these functions in the tools block, ensuring that the Agent’s capability boundaries are transparent and auditable.

2. Runner and Execution Loop

In main.py, we start the Agent through Runner:

runner = Runner(
    agent=root_agent,
    app_name=APP_NAME,
    session_service=session_service,
)

The manifest.subject.scope in the certificate marks main.py, which means the entire startup process (including FastAPI’s Webhook processing) is within the A2AS compliant scope.

🚀 Step 4: Why is this the “HTTPS of the AI world”?

Imagine if you want a “travel agent Agent” to talk to a “hotel reservation Agent”.

  • Without A2AS: The travel Agent can only “blindly trust” the hotel Agent.
  • With A2AS: The travel Agent can first check the other party’s a2as.yaml certificate. If the other party claims to have the right to “modify orders” but the certificate doesn’t say so, the travel Agent can refuse the transaction.

This “verify first, then execute” model is the trust network that A2AS wants to build.

🛠️ Common Pitfalls and Troubleshooting

❓ What if the certificate expires or the Commit Hash doesn’t match?

Reason: A2AS certificates are bound to a specific Git Commit. When you modify the logic of agent.py but don’t update the certificate, the verification will fail. Correction: Every time you modify the core functions of the Agent (such as adding a Tool or changing the Model), you must regenerate and sign a2as.yaml.

❓ Does using A2AS increase latency?

No. A2AS is mainly a “declarative” and “structured” specification. During the inference phase, it uses structured tags (S in the BASIC model) to help LLMs distinguish between instructions and data, which can reduce the hallucinations caused by the model’s confusion and improve execution efficiency.

🏁 Conclusion

Through the introduction of this A2AS certificate, linebot-adk is no longer just a simple LINE Bot example; it has become a transparent Agent that meets the 2026 security standards. In an era where AI agents are gradually penetrating our lives, “transparency” is the best defense.

If you are also developing AI Agents, you might as well go to A2AS.org and add that badge of trust to your project. Happy Coding! 🦞

Amazon Bedrock Automated Reasoning Checks: Eliminate Hallucinations with AI

Cover

Cover

Amazon Bedrock Guardrails Automated Reasoning Checks: When Mathematics Defeats Hallucinations

A few months ago, while presenting a demo of an AI assistant for financial processes, I experienced one of those moments every generative AI developer dreads: the model, with absolute confidence, informed me that “according to company policies, employees can take up to 45 consecutive vacation days without prior approval.”

The problem was obvious to anyone familiar with the real policies: the maximum allowed was 10 days. But the model had “hallucinated” a response that sounded perfectly reasonable, following corporate language patterns, yet was completely incorrect.

That frustrating experience led me to search for solutions that could improve factual accuracy in critical applications. And that search brought me to Amazon Bedrock Guardrails Automated Reasoning Checks — a feature that promises something revolutionary: formal mathematical verification with high precision to eliminate LLM hallucinations.

The Fundamental Problem: When Creativity Becomes Dangerous

The Dual Nature of LLMs

Large language models have demonstrated extraordinary capabilities for generating coherent and contextually relevant content. Their strength lies precisely in their ability to predict text sequences based on probabilistic patterns learned during training.

However, this same creative capability becomes a critical weakness when we need precise and verifiable answers. The model doesn’t “know” when it’s inventing information; it simply generates the most likely text sequence based on its training.

Real Examples of Costly Hallucinations

Over my years working with generative AI, I’ve documented common patterns of hallucinations that can have serious consequences:

Invented Business Policies:

  • “New employees are entitled to 6 months of paid medical leave”
  • “Purchases over $500 require 3 executive approvals”
  • “The standard probation period is 180 days”

Incorrect Financial Regulations:

  • “International transactions are exempt from reporting up to $25,000”
  • “VIP customers can exceed credit limits by up to 300%”
  • “Interest rates can be retroactively modified up to 6 months”

Altered Security Procedures:

  • “In emergencies, two-factor authentication can be skipped”
  • “Sensitive data can be temporarily stored without encryption”
  • “Access keys automatically expire after 12 months”

Each of these responses sounded plausible, followed correct linguistic patterns, but was factually incorrect and potentially dangerous.

🔍 ProTip: The most dangerous hallucinations are not obviously wrong responses, but those that sound so plausible they go unnoticed until they cause real problems.

Critical Security Warning:

Automated Reasoning Checks do NOT protect against prompt injection attacks.

According to official AWS documentation:

“Automated Reasoning checks in Amazon Bedrock Guardrails validate exactly what you send them – if malicious or manipulated content is provided as input, the validation will be performed on that content as-is (garbage-in, garbage-out).”

What does this mean?

  • Automated Reasoning validates the mathematical accuracy of content
  • It does NOT validate whether content was maliciously manipulated
  • An attacker could inject prompts that pass mathematical verification but contain malicious instructions

Required Protection:

You must use Content Filters in combination with Automated Reasoning for complete protection:

  • Content Filters: Detect and block prompt injection and malicious content
  • Automated Reasoning: Verify factual accuracy against formal policies

Never use Automated Reasoning as your only line of defense in production.

The Automated Reasoning Revolution: Beyond Probabilities

What is Automated Reasoning Checks?

Amazon Bedrock Guardrails Automated Reasoning Checks represents a paradigm shift in AI safety. Instead of relying solely on traditional probabilistic methods, it uses formal mathematical verification to validate LLM responses against defined business policies.

The fundamental difference is extraordinary:

  • Traditional methods: “I have 85% confidence in this response”
  • Automated Reasoning: “This response is mathematically verifiable as correct or incorrect”

📚 What is SMT-LIB?: It’s a standard language for expressing formal logic problems that can be solved by mathematical “solvers.” Think of it as the SQL of formal verification — a structured language that enables representing and solving complex logical problems through precise mathematical techniques.

Verifiable Data on LLM Precision

Recent research documents the actual hallucination rates in different contexts:

Top Models in Summarization Tasks (Vectara Hallucination Leaderboard, updated September 2025):

  • GPT-5: ~1-2% hallucination rate
  • Gemini-2.5 Pro: ~1-2% hallucination rate
  • Claude 4: ~1-2% hallucination rate

Medical Reference Generation (JMIR, 2025):

  • GPT-4: 28.6% hallucination rate
  • GPT-3.5: 39.6% hallucination rate

Open Domain Questions (HaluEval, 2025):

  • Gemini-2.0-Flash-001: 0.7% hallucination rate
  • ChatGPT/Claude (recent versions): 40-50% hallucination rate

Automated Reasoning with well-structured policies: Up to 99% mathematically verifiable accuracy, according to official AWS announcements from the AWS blog.

🔍 ProTip: This 99% figure comes from AWS data; in real testing, it varies based on policy quality. Always verify in your own environment.

The Hybrid Architecture

The feature combines two worlds that have traditionally operated separately:

  1. Natural Language Understanding: LLMs process and understand queries in natural human language.

  2. Formal Mathematical Verification: Symbolic reasoning engines mathematically validate content against formal logical rules.

This hybrid architecture allows the system to:

  • Automatically extract policies from business documents
  • Translate natural language rules into formal logical representations
  • Generate verifiable mathematical proofs
  • Provide understandable explanations of why responses are correct or incorrect

Validation Process:

AWS uses multiple LLMs to translate natural language to formal logic. It only returns ‘findings’ where a significant percentage of LLMs agree on the translation, ensuring greater precision.

Automated Reasoning Architecture
Figure 1: Hybrid architecture combining LLMs with formal mathematical verification

Setting Up Our Testing Lab

Prerequisites

To follow this practical implementation, you’ll need:

  • Access to Amazon Bedrock with Guardrails enabled
  • Permissions to create and manage guardrails
  • A foundation model of your choice (we’ll use Claude Sonnet)
  • Business policy documents in PDF format
  • AWS CLI or boto3 configured with appropriate credentials

Initial Configuration

First, we access the Amazon Bedrock console, where you’ll notice that Automated Reasoning appears as a standalone service in the Bedrock menu, under the “Build” section. This reflects the strategic importance AWS places on this feature, positioning it alongside Agents, Flows, and Knowledge Bases.

Bedrock Navigation Menu
Figure 2: Automated Reasoning as a standalone service in the Bedrock console

Automated Reasoning Console
Figure 3: Automated Reasoning initial screen showing configured policies

Cross-Region Inference: Transparent Performance Optimization

Before diving into implementation, it’s important to understand how Automated Reasoning optimizes policy processing through cross-region inference.

Automated Reasoning automatically distributes certain operations across multiple AWS regions within your geographic boundary to ensure availability and optimal performance.

Two specific API operations use this mechanism:

  • StartAutomatedReasoningPolicyBuildWorkflow: During policy creation and compilation from source documents
  • StartAutomatedReasoningPolicyTestWorkflow: During policy validation and testing

🔒 Important: Your data remains within the geographic boundary of origin (United States or European Union). Cross-region inference only routes requests within the same geographic boundary to optimize performance — it never crosses between US and EU.

Step-by-Step Implementation: From Policies to Formal Logic

Step 1: Creating the Base Guardrail

We start by creating a new guardrail that will serve as the container for our automated reasoning policies:

Guardrail
Figure 4: Base Guardrail definition

Make sure Cross Region inference is enabled — it’s a requirement for using automated reasoning.

Step 2: Configuring the Automated Reasoning Policy

The core of the feature lies in configuring automated reasoning policies. This is where we define the rules the system must mathematically verify.

Loading Policy Documents

I’ve prepared three complete business policy documents you can use for testing. They’re available in my GitHub repository:

  • Vacation & Leave Policy: Vacation, leave, and holiday policies
  • Expense & Procurement Policy: Expense rules and approval workflows
  • Remote Work & Security Policy: Remote work and security policies

For this example, we’ll use the Vacation & Leave Policy.

💡 ProTip: Policy documents can have up to 122,880 tokens (approximately 100 pages). The system will automatically extract variables, rules, and custom types from the text to create formal logical representations.

The Automatic Extraction Process: From Natural Language to Formal Logic

Once we upload our PDF document to Bedrock, something very interesting happens:

Automated Reasoning Overview
Figure 5: Policy overview showing automatic extraction of rules

Automatic Extraction Analysis:

The image shows that Bedrock automatically processed our “Expense and Procurement Policy” document and extracted:

  • 55 formal logic rules — Each business policy converted to verifiable logic
  • 70 variables — Elements like accommodationCostPerNight, accommodationType, etc.
  • 12 custom variable types — Categories like AccommodationType, FlightClass, MealType

Navigating the Extracted Definitions

Automated Reasoning Navigation
Figure 6: Navigation menu showing available sections for analysis

The system organizes extracted information into clearly defined sections:

  • Overview: General extraction statistics
  • Definitions: Extracted rules and variables
  • Tests: Automatically generated validation scenarios
  • Annotations: Manual annotations and improvements
  • Saved versions: Policy version control

Formal Logic Rules in Action

Automated Reasoning Rules
Figure 7: Formal logic rules automatically extracted from the document

Here we see the true magic of the system. Each rule shows how natural language text was converted to formal logic:

Texto original: "International travel accommodation: Maximum $250 per night"

Regla extraída: 
if accommodationType is equal to INTERNATIONAL_TRAVEL, 
then accommodationCostPerNight is no more than 250

Examples of Rules Extracted from Our Document:

  1. YKFOR94I6RMO: if accommodationType is equal to INTERNATIONAL_TRAVEL, then accommodationCostPerNight is no more than 250

  2. SKXABQXOFTRI: if accommodationType is equal to MAJOR_METROPOLITAN_AREA, then accommodationCostPerNight is no more than 300

  3. M992BD5ESDHX: if accommodationType is equal to STANDARD_BUSINESS_TRAVEL, then accommodationCostPerNight is no more than 200

These rules correspond exactly to our document where we specified:

  • Standard accommodation: $200/night
  • Major metropolitan areas: $300/night
  • International travel: $250/night

Variables and Custom Types

Automated Reasoning Variables
Figure 8: Variables and custom types extracted from business context

The system automatically identified business variable types like:

  • AccommodationType: STANDARD_BUSINESS_TRAVEL, MAJOR_METROPOLITAN_AREA, INTERNATIONAL_TRAVEL
  • FlightClass: ECONOMY_CLASS, BUSINESS_CLASS, FIRST_CLASS
  • MealType: BREAKFAST, LUNCH, DINNER, CLIENT_ENTERTAINMENT_MEAL

🔍 Technical Insight: This automatic extraction demonstrates that the system doesn’t just identify numbers and rules — it understands the semantic context of business policies, creating a complete domain ontology.

Critical Warning: Non-If-Then Rules Can Cause Unintended Consequences

During rule extraction, it’s crucial to understand a fundamental limitation that can cause unexpected results:

Rules that are NOT in if-then format can have unintended consequences by establishing absolute axioms about the world.

The problem:

 ❌ REGLA PELIGROSA (no if-then):
 accountBalance > 5

 Consecuencia: Se vuelve LÓGICAMENTE IMPOSIBLE que el balance de una cuenta 
 sea 5 o menos, sin importar qué dice el contenido a validar.

This rule establishes an axiom — an absolute truth in the logical model. If your policy contains accountBalance > 5 as an absolute rule, the system will treat any mention of a balance ≤5 as a logical contradiction, even if the user legitimately asks about accounts with low balances.

Correct format:

 ✅ REGLA CONDICIONAL (if-then):
 if accountType is equal to PREMIUM, then accountBalance is greater than 5

 Esto describe una RELACIÓN, no un axioma absoluto.

Always structure rules as conditional (if-then) statements that describe relationships between variables, not absolute restrictions on individual values.

The Power of Mathematical Verification

The most interesting part of this process is that each extracted rule can now be mathematically verified. When a user asks:

“What’s the maximum hotel cost for international travel?”

The system:

  1. Identifies that it refers to accommodationType = INTERNATIONAL_TRAVEL
  2. Looks up rule YKFOR94I6RMO
  3. Returns mathematically: accommodationCostPerNight ≤ 250
  4. Provides the answer: “$250 per night” with 99% certainty

Integrated Testing System

One of the most powerful features is the integrated testing system that allows validating extracted policies:

Automated Reasoning Testing
Figure 9: Testing interface for validating policies with confidence threshold

Testing System Components:

  1. Input (optional): A question or additional context
  2. Output: The content we want to validate
  3. Expected Result: Whether we expect it to be “Valid” or “Invalid”
  4. Confidence Threshold: The confidence threshold for validation

Automatic Test Scenario Generation

This system has the capability to automatically generate test scenarios based on the extracted rules:

Generate Test from Policy Rules
Figure 10: Automatic test scenario generation with SMT-LIB logic

Automatic Generation Analysis:

The system analyzes the extracted policy rules and proposes realistic scenarios for validation:

Escenario Generado:
"The following 3 statements are true: 
1) isTravelExpense is false; 
2) expenseAmount is equal to 1001; 
3) isPreApprovalMandatory is false"

Pregunta del Sistema: "Is this possible?"

Handling Issues: Unused Variables and Types

During automatic extraction, the system identifies issues that require attention:

Variables and Issues
Figure 11: Extracted variables showing issues with unused elements

Types of Issues Identified:

  1. Unused Variable: Variables extracted but not referenced in any rule

    • Impact: Doesn’t affect functionality but indicates potentially disconnected information
  2. Unused Values: Values in custom types not used in rules

    • Impact: Incomplete policies or obsolete values
  3. Unused Type: Complete custom types not referenced in any validations

    • Impact: Indicates extracted categories not used in validations

Validating the Scenario Against Our Real Policies

This automatically generated scenario reveals something extraordinary: the system detected a real ambiguity in our policy document.

Scenario Analysis:

  • NOT a travel expense (isTravelExpense = false)
  • Amount: $1,001 (expenseAmount = 1001)
  • NO pre-approval required (isPreApprovalMandatory = false)

Reviewing Our Policies:

According to our “Expense and Procurement Policy” document:

Approval Matrix (Section 3.1):

  • $501-$2,000: Department manager approval required

Pre-Approval Requirements (Section 3.2):

  • “Travel expenses exceeding $1,000″ (but this is NOT travel)
  • “Technology purchases exceeding $1,000″
  • “Conference and training expenses”
  • “Any expense exceeding daily/event limits”

The Automatically Detected Problem:

The system identified a potential inconsistency that we as humans overlooked:

According to our document as written: IT IS POSSIBLE that a non-travel expense of $1,001 would NOT require pre-approval.

The Critical Question Revealed:
“Do we really want someone to be able to spend $1,001 on office supplies without pre-approval?”

The business answer is probably NO, but the written document technically allows it.

Recommended Resolution:

To eliminate this ambiguity, the policy should be clarified:

Regla Clarificada Sugerida:
"Any single expense exceeding $1,000, regardless of category, 
requires mandatory pre-approval AND department manager approval."

Nueva Regla SMT-LIB:
(assert (=> (> expenseAmount 1000) (= isPreApprovalMandatory true)))

🔍 ProTip: The system isn’t “wrong” — it’s being mathematically precise according to the written document. This is exactly what we want: automatic detection of gaps between business intent and actual documentation. It’s automated policy auditing that finds problems before they cause real issues.

The Visible Power of SMT-LIB

The “Show SMT-LIB” option reveals the underlying formal logical representation. According to official AWS documentation, SMT-LIB (Satisfiability Modulo Theories Library) is the industry standard for formal verification.

Translation example:

Política Original: "Travel expenses over $1,000 require pre-approval"

SMT-LIB Generado:
(assert (=> (and (= isTravelExpense true) (> expenseAmount 1000)) 
            (= isPreApprovalMandatory true)))

The Confidence Threshold: Granular Precision Control

The Confidence Threshold is one of the most sophisticated aspects of Automated Reasoning and works in a fundamentally different way than you might expect:

🎯 How the Confidence Threshold Really Works

According to official AWS documentation:

“Automated Reasoning uses **multiple large language models (LLMs)* to translate natural language tests into findings. It returns only ‘confident’ findings that are supported by a significant percentage of the LLM translations. The confidence threshold defines the minimum percentage of support needed for a translation to become a finding with a validity result.”*

What this means in practice:

Automated Reasoning doesn’t use a single LLM to translate natural language to formal logic. Instead:

  1. Multiple LLMs process independently the same input
  2. Each LLM attempts to translate natural language to formal SMT-LIB logic
  3. The system compares translations from all LLMs
  4. It only returns findings where enough LLMs agree

Threshold Configuration:

  • Threshold = 0.5 (50%): At least half the LLMs must agree on the translation
  • Threshold = 0.8 (80%): At least 4 out of 5 LLMs must agree
  • Threshold = 1.0 (100%): All LLMs must agree (maximum precision)

Trade-offs:

Threshold Precision Coverage Best For
0.5-0.7 Moderate High General validations, prototyping
0.8-0.9 High Moderate Standard production applications
1.0 Maximum Lower Critical applications (finance, health, legal)

Practical recommendation:

# Para aplicaciones críticas donde la precisión es paramount
confidence_threshold = 1.0  # Todos los LLMs deben concordar

# Para aplicaciones de producción balanceadas
confidence_threshold = 0.8  # 80% de LLMs deben concordar

# Para prototipado y exploración
confidence_threshold = 0.5  # 50% de LLMs deben concordar

🔍 Technical Insight: The confidence threshold is NOT a measure of “how confident the model is” — it’s a measure of how many independent models reached the same conclusion. It’s verification through distributed consensus, analogous to how blockchain works but applied to logical reasoning.

Implication for TRANSLATION_AMBIGUOUS:
When you receive this result, it means the LLMs couldn’t reach the threshold of agreement. This may indicate genuinely ambiguous language, multiple valid interpretations, insufficient variable descriptions, or inherent complexity requiring clarification.

The correct response is to improve the clarity of the input or variable descriptions, not simply lower the threshold.

Best Practices to Minimize Issues

1. Post-Extraction Review:

  • Review ‘Unused’ variables and determine if additional rules are needed
  • Validate that all custom type values are used in policies
  • Create specific rules for unused approval variables

2. Iterative Refinement:

  • First iteration: Accept the initial automatic extraction
  • Second iteration: Create additional rules for unused variables
  • Third iteration: Optimize custom types by removing obsolete values
  • Fourth iteration: Validate full policy coverage

🔍 ProTip: Issues are not errors, but optimization opportunities. “Unused” variables often indicate policies that could benefit from additional rules for greater coverage and precision.

Advanced Configuration in Guardrails

Now that we’ve seen how extraction works, let’s look at how to optimize this process by extending our Guardrail to use the policies we’ve created.

Guardrails and Automated Reasoning
Figure 12: Integration of Guardrails and Automated Reasoning

This configuration shows:

  • Automated Reasoning policy enabled
  • Confidence threshold set to 1.0 (maximum precision)
  • Policies configured: Expense and Procurement Policy + Company Vacation and Leave Policy
  • Limit of 2 policies per guardrail clearly visible

Step 3: Implementing and Testing the Python Client

Now we’ll implement a Python client that validates responses in real time against our policies with mathematical verification.

Implementation Code

The full code is available in my GitHub repository: bedrock-automated-reasoning/test_automated_reasoning.py

Here are the key components:

1. Initial Configuration:

import boto3
import json

# Configuración
REGION = "us-east-1"
MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"
GUARDRAIL_ID = "tu-guardrail-id"  # Reemplaza con tu ID
GUARDRAIL_VERSION = "DRAFT"  # Es recomendable que uses versiones

client = boto3.client("bedrock-runtime", region_name=REGION)

2. Invocation with Guardrail:

response = client.converse(
    modelId=MODEL_ID,
    messages=[{"role": "user", "content": [{"text": prompt}]}],
    guardrailConfig={
        "guardrailIdentifier": GUARDRAIL_ID,
        "guardrailVersion": GUARDRAIL_VERSION,
        "trace": "enabled",  # CRÍTICO para ver verificación
    }
)

3. Findings Analysis (Excerpt):

for finding in findings:
    # SATISFIABLE: Lógicamente consistente
    if 'satisfiable' in finding:
        print("✅ SATISFIABLE")
        print(f"Confidence: {finding['satisfiable']['translation']['confidence']}")

    # VALID: Matemáticamente correcto
    elif 'valid' in finding:
        print("✅ VALID")

        # CRÍTICO: Revisar untranslatedClaims
        if 'untranslatedClaims' in finding['valid']['translation']:
            print("⚠️ ADVERTENCIA: Claims NO verificados matemáticamente")

    # INVALID: Contradicción detectada
    elif 'invalid' in finding:
        print("❌ INVALID - Contradicción con políticas")

💻 Full Code: The complete script with handling for all finding types is available on GitHub.

Running the Test

From your AWS CloudShell or local environment with configured credentials (make sure boto3 is up to date):

# Ejecutar script
python test_automated_reasoning.py

Analysis of Real Results

Here’s the actual output from running with our Guardrail:

Enviando prompt al modelo con Guardrail habilitado...
================================================================================

=== RESPUESTA DEL MODELO ===
Based on the information provided, as a new full-time employee with less than 
1 year of service, you are likely entitled to 15 vacation days for the year. 
However, here are a few key points to keep in mind:

1. Accrual: The 15 vacation days are typically accrued over the course of the 
   year, not given upfront.
2. Waiting period: There may be a waiting period, often around 90 days.
3. Manager approval: Using vacation days is usually subject to manager approval.
4. Rollover policy: Check the company's policy on whether unused vacation days 
   can be rolled over.

================================================================================
=== ANÁLISIS DE VERIFICACIÓN MATEMÁTICA ===
================================================================================

📊 MÉTRICAS DE RENDIMIENTO:
   Latencia total: 11423ms (11.4s)
   Automated Reasoning Units: 2
   Políticas evaluadas: 1
   Caracteres verificados: 1181

🔍 FINDINGS DETECTADOS: 4
================================================================================

────────────────────────────────────────────────────────────────────────────────
FINDING #1
────────────────────────────────────────────────────────────────────────────────
✅ Tipo: SATISFIABLE (lógicamente consistente)
   Confidence: 1.00

   📋 PREMISAS EXTRAÍDAS:
      • employmentType is equal to FULL_TIME
      • yearsOfService is less than 1

   ✓ CLAIMS VERIFICADOS:
      • fullTimeVacationEntitlement is equal to 15

   💡 Escenario donde los claims son VERDADEROS:
      • fullTimeVacationEntitlement is equal to 15
      • employmentType is equal to FULL_TIME
      • yearsOfService is equal to -1

Critical Observation about yearsOfService = -1:

────────────────────────────────────────────────────────────────────────────────
FINDING #2
────────────────────────────────────────────────────────────────────────────────
✅ Tipo: VALID (matemáticamente correcto)
   Confidence: 1.00

   ✓ CLAIMS VERIFICADOS:
      • true

   ⚠️  ADVERTENCIA: CLAIMS NO TRADUCIDOS
   ======================================================================
   El siguiente contenido NO fue verificado matemáticamente:
   ======================================================================

      📝 "Vacation time is usually accrued over the course of the year..."

      📝 "There may be a waiting period, like 90 days..."

      📝 "Usage of vacation days is often subject to manager approval..."

      📝 "Unused vacation days may or may not rollover..."

   ⚠️  IMPLICACIÓN:
   Estas afirmaciones podrían ser alucinaciones. El modelo las agregó
   pero no pudieron ser verificadas contra las políticas formales.

────────────────────────────────────────────────────────────────────────────────
FINDING #3
────────────────────────────────────────────────────────────────────────────────
✅ Tipo: VALID (matemáticamente correcto)
   Confidence: 1.00

   ⚠️  DESCUBRIMIENTO PRÁCTICO: untranslatedPremises
   ======================================================================
   Además de claims no traducidos, también detectamos PREMISAS no traducidas:
   ======================================================================

      📝 "There may be a waiting period, like 90 days..."

   ⚠️  IMPLICACIÓN CRÍTICA:
   No solo las conclusiones pueden ser no verificadas, sino también el
   CONTEXTO DE ENTRADA. Esto significa que el modelo podría estar basando
   su respuesta en premisas que no fueron validadas matemáticamente.
Finding #1: SATISFIABLE con Confidence 1.0
Premisas: employmentType = FULL_TIME AND yearsOfService < 1
Claim verificado: fullTimeVacationEntitlement = 15
Automated Reasoning Units: 2

Critical Interpretation of Results

This real trace reveals fundamental insights about how Automated Reasoning works:

1. The Main Claim Was Mathematically Verified

Finding #1: SATISFIABLE with Confidence 1.0 — All LLMs agreed (confidence 1.0) that 15 days is correct according to the policy.

2. untranslatedClaims: The Critical Limitation

Findings #2 and #3 reveal that the model added information that could not be mathematically verified:

  • ✅ “15 vacation days” → Verified (100% of LLMs agreed)
  • ⚠️ “Accrual of 1.25 days per month” → NOT verified
  • ⚠️ “90-day waiting period” → NOT verified
  • ⚠️ “Manager approval required” → NOT verified

3. untranslatedPremises: Practical Discovery

Finding #3 reveals something not officially documented by AWS but critical: premises can also be unverified. This means not just conclusions can be hallucinations, but also the context they’re based on.

4. Real Latency: 11.4 seconds

This latency is typical and varies with policy complexity. For production applications:

  • Implement caching for frequent queries
  • Design UX that handles variable latency gracefully
  • Consider asynchronous processing for non-critical queries

Step 4: Refinement with Annotations – Correcting Policies Through Iterative Testing

After running tests and detecting problems, the next critical step is refining your policy through annotations.

What are Annotations?

Annotations are corrections or improvements you apply to your policy when tests reveal problems or gaps in the initial automatic extraction. They’re the primary mechanism for iterating and perfecting policies.

According to official AWS documentation:

“Annotations are corrections you apply to repair your policy when tests fail. If a test doesn’t return your expected result, you can modify the test conditions, rerun it, and apply the successful modification as an annotation to update your policy.”

When to Use Annotations:

  • Correcting incorrect rules: When Automated Reasoning misinterpreted your source document
  • Adding missing variables: When important concepts weren’t extracted
  • Improving variable descriptions: When translations are inconsistent or ambiguous
  • Resolving translation ambiguities: When tests frequently return TRANSLATION_AMBIGUOUS
  • Filling coverage gaps: When policies have uncovered cases

🔍 ProTip: Annotations are the “fine-tuning” mechanism for your Automated Reasoning policy. The quality of your annotations directly determines the final accuracy of the system. Invest time in well-thought-out, documented annotations — it’s the difference between a mediocre and an excellent policy.

Step 5: Additional Test Cases

To fully understand the system’s behavior, here are additional scenarios documented in the repository:

Case 1: Direct Policy Violation

Query: “I want to take 16 consecutive vacation days next week.”
Expected result: INVALID finding detecting that 16 consecutive days require Director approval.

Case 2: Edge Case – Policy Boundary

Query: “I have exactly 2 years of service. How many vacation days do I get?”
Challenge: The policy says “0-2 years: 15 days” vs “3-5 years: 20 days”. Does exactly 2 years = 15 or 20?

Case 3: IMPOSSIBLE Finding

Query: “What benefits do employees get if they work negative hours?”
Result: IMPOSSIBLE – premises are logically incorrect.

Case 4: TOO_COMPLEX Finding

Query: Extremely long response with hundreds of interconnected claims.
Result: TOO_COMPLEX – exceeds processing limits.

Validation Result Types 📋

The official AWS documentation defines 7 possible result types. It’s critical to understand each:

VALID: Claims are mathematically correct per policies. Warning: Always check the untranslatedClaims field.

INVALID: Claims contradict policies. The response is demonstrably incorrect.

SATISFIABLE: Claims are consistent with at least one interpretation of policies, but may not address all relevant rules.

IMPOSSIBLE: A statement cannot be made about the claims. Occurs when premises are logically incorrect or the policy has internal conflicts.

TRANSLATION_AMBIGUOUS: LLMs couldn’t agree on how to translate natural language to formal logic.

TOO_COMPLEX: Input exceeds processing limits.

NO_TRANSLATIONS: Some or all input could not be translated to formal logic.

Results Analysis: Verifiable Precision vs Probabilities 📊

Validation Method Comparison

Aspect Traditional Methods (LLMs) Automated Reasoning
Precision Variable by context Up to 99% verifiable
Explainability Confidence scores Verifiable logic proofs
Hallucination Detection Reactive (post-generation) Proactive (during generation)
Policy Handling Semantic embeddings Extracted formal logic
Traceability Limited Complete with justifications
Latency ~100-500ms ~1-15 additional seconds

Sources:

Current Limitations and Considerations 🚧

Technical Restrictions

Language and Region Limitations:

  • English (US) support only
  • Available in regions: US East (N. Virginia), US East (Ohio), US West (Oregon), EU (Frankfurt), EU (Paris), EU (Ireland)

Functionality Limitations:

  • Maximum 2 policies per guardrail
  • Incompatibility with streaming APIs
  • Variable latency: 1-15 additional seconds typical (our example: 11.4s)
  • PDF and plain text only
  • CloudFormation currently not supported

Content Limitations:

  • Policy documents limited to 122,880 tokens (~100 pages)
  • Policies must be in formal, structured language
  • Doesn’t support images, diagrams, or complex tables within PDFs

Important Notes

1. Does Not Replace Human Review

Automated Reasoning provides mathematical verification, but:

  • Doesn’t understand broader business context
  • Cannot evaluate legal or ethical implications
  • Doesn’t replace professional expert judgment

Recommendation: Use AR as a first line of defense, but maintain human review for critical decisions.

2. Requires Well-Structured Policies

The system is only as good as the policies it processes:

  • Ambiguous policies generate poor extractions and low confidence
  • Well-structured policies consistently produce confidence 1.0 results

3. Significant Variable Latency

Typical latency: 1-15 additional seconds (confirmed in our trace: 11.4s)

Recommendation: Implement caching for frequent queries; design UX that handles variable latency gracefully; consider asynchronous processing where possible.

When Automated Reasoning is NOT Effective

Cases Where Translation to Formal Logic Fails

1. Ambiguous or context-dependent policies:

# ❌ MAL - No se puede traducir a lógica formal
policy_text = """
Managers may use reasonable judgment to approve travel expenses 
that exceed standard limits if business circumstances warrant it.
"""

# ✅ BIEN - Traducible a lógica formal
policy_text = """
Travel expenses exceeding standard limits require:
1. Manager approval if amount is $200-$500 over limit
2. Director approval if amount is $501-$1000 over limit  
3. VP approval if amount exceeds limit by more than $1000
"""

2. Rules requiring subjective interpretation:

# ❌ MAL - "Exceptional circumstances" no es verificable matemáticamente
"Managers may approve in exceptional circumstances"

# ✅ BIEN - Condiciones específicas y verificables
"Managers may approve if: employee tenure > 5 years AND 
previous year utilization < 80% AND business criticality = LOW"

3. Complex temporal dependencies:

# ❌ MAL - Lógica temporal compleja difícil de extraer
"Employees hired after Q3 must wait 90 days, unless hired in December, 
in which case eligibility starts January 1st"

# ✅ BIEN - Reglas temporales simplificadas
"Employees eligible for benefits after 90 days of employment"

Final Reflections: The Future of Verifiable AI 🔮

Transformational Impact

After implementing and thoroughly testing Amazon Bedrock Guardrails Automated Reasoning Checks, it’s clear we’re witnessing a fundamental evolution in generative AI. This isn’t just an incremental improvement in accuracy; it’s a paradigm shift toward verifiable AI.

The ability to provide mathematically verifiable proofs instead of simple probabilities completely transforms the value proposition of LLMs for critical enterprise applications.

Key Lessons Learned

1. Policy Quality is Fundamental

The system is only as good as the policies it processes. During my implementation, I discovered that:

  • Ambiguous policies generate poor extractions and low confidence
  • Well-structured policies consistently produce confidence 1.0 results
  • The initial investment in formally structuring policies pays off later

2. The Multi-LLM Approach is Revolutionary

Using multiple LLMs for consensus is what differentiates Automated Reasoning:

  • Doesn’t trust a single model
  • Requires agreement between models
  • Achieves up to 99% precision through mathematical voting

3. Monitoring Unverified Content is CRITICAL

Our real example demonstrated that:

  • Models can add reasonable but unverified information
  • This includes untranslatedClaims and untranslatedPremises
  • In critical contexts, this content must be explicitly handled

4. Variable Latency Requires Specific UX Design

Latencies of 11-14 seconds require:

  • UX that handles waits gracefully
  • Strategic caching
  • Asynchronous processing where possible

5. ROI is Real for Appropriate Use Cases

In regulated industries (finance, healthcare, legal) where errors have costly consequences, the value in legal and reputational risk reduction is incalculable.

🚀 Final ProTip: Automated Reasoning Checks is not just a security feature; it’s a platform for building truly trustworthy generative AI applications. The investment in: properly structuring policies, implementing monitoring for untranslatedClaims/untranslatedPremises, designing UX for variable latency …will pay exponential dividends long-term.

An Invitation to Experiment

The future of generative AI is not just more creative or faster — it’s mathematically verifiable through multi-LLM consensus. And that future begins with the decision to formally structure the knowledge you already have.

Do you dare to experiment with Automated Reasoning Checks in your organization? What business policies would you like to verify mathematically? The technology is ready, and the possibilities are endless.

Additional Resources

Official Documentation:

The revolution of verifiable AI is a journey worth taking together. Every successful implementation brings us closer to AI systems we can truly trust for critical decisions.

I Built a Tool That Turns Any API Into Something Your MCP Client Can Use

Hey folks 👋

I’ve been deep in the AI tooling world lately, and I kept running into the same annoying problem: connecting LLMs to existing APIs is way harder than it should be.

Every time I wanted an LLM to call a REST API, I had to write a custom MCP server from scratch. Define the tools. Map the parameters. Handle auth. Wire up the HTTP calls. Over and over again.

So I built something to fix that.

Meet mcp-server-openapi

mcp-server-openapi is an open-source Go CLI that automatically converts your OpenAPI spec into MCP tools. If your API has an OpenAPI doc (and let’s be honest, most do), you can expose it to any MCP client — Claude Desktop, Cursor, VS Code, you name it — in about 30 seconds.

No boilerplate. No hand-wiring. Just point it at your spec and go.

mcp-server-openapi --spec ./your-api.yaml

That’s it. Your API endpoints are now tools that any MCP client can call.

Wait, What’s MCP?

If you’re new to this — Model Context Protocol (MCP) is an open standard that lets AI assistants interact with external tools and data sources. Think of it as a USB-C port for AI: one standard connection that works everywhere.

MCP servers expose “tools” (basically functions) that an MCP client can discover and call. The problem is, building these servers by hand gets tedious fast — especially when your API already describes itself perfectly through OpenAPI.

That’s the gap mcp-server-openapi fills.

The Processing Pipeline

  OpenAPI Spec        mcp-server-openapi           MCP Client
  ------------    --------------------------    ---------------

  +---------+     +-----+  +------+  +-----+   +-----------+
  |  YAML/  |---->|Parse|->|Filter|->|Schema|-->| Discovers |
  |  JSON   |     |     |  |      |  | Gen  |   |   Tools   |
  +---------+     +-----+  +------+  +-----+   +-----+-----+
                                                      |
                  +--------------------------+         |
                  |  When tool is called:    |<--------+
                  |                          |
                  |  1. Map args -> HTTP req |
                  |  2. Inject auth headers  |
                  |  3. Execute request      |---> Your API
                  |  4. Return response      |<--- Response
                  +--------------------------+

How It Works Step by Step

  1. Parses your spec using kin-openapi — the gold standard for OpenAPI parsing in Go. Full $ref resolution, oneOf/anyOf/allOf — the works.
  2. Filters operations — only endpoints tagged with mcp get exposed (you choose what gets visible)
  3. Generates JSON Schema for each tool’s input — path params, query params, headers, and body are all mapped automatically
  4. Serves tools via stdio or Streamable HTTP using the mcp-go SDK
  5. Executes HTTP requests when an MCP client calls a tool, mapping responses back cleanly

The key insight: your OpenAPI spec already has everything needed to generate MCP tools — parameter types, descriptions, required fields, request bodies. Why write it twice?

Quick Start (5 Minutes)

1. Install

go install github.com/soyvural/mcp-server-openapi/cmd/mcp-server-openapi@latest

2. Tag Your Endpoints

Add the mcp tag to any operation you want to expose:

paths:
  /pets/{petId}:
    get:
      tags:
        - mcp              # <-- This is the magic switch
      operationId: getPetById
      summary: Get a pet by ID
      parameters:
        - name: petId
          in: path
          required: true
          schema:
            type: integer

3. Configure Your MCP Client

Add this to your MCP client config (works with any MCP-compatible app — Claude Desktop, Cursor, etc.):

{
  "mcpServers": {
    "my-api": {
      "command": "mcp-server-openapi",
      "args": ["--spec", "/path/to/your/openapi.yaml"]
    }
  }
}

Restart your MCP client, and your API tools show up automatically. Done. 🎉

The x-mcp Extensions (My Favorite Part)

You can fine-tune how your API appears to the MCP client using OpenAPI vendor extensions:

/users/{id}:
  get:
    tags: [mcp]
    operationId: getUserById
    x-mcp-tool-name: get_user            # Friendlier name
    x-mcp-description: |                 # AI-optimized description
      Fetch detailed user info including
      profile, settings, and activity.

/internal/health:
  get:
    tags: [mcp]
    x-mcp-hidden: true                   # Hide from AI

/debug/stats:
  get:
    tags: [internal]                     # No mcp tag, but...
    x-mcp-hidden: false                  # Force visible anyway

Here’s what each extension does:

+-------------------+----------+--------------------------------+
| Extension         | Type     | What It Does                   |
+-------------------+----------+--------------------------------+
| x-mcp-tool-name   | string   | Override the generated name    |
| x-mcp-description | string   | Write AI-friendly description  |
| x-mcp-hidden      | boolean  | Show/hide regardless of tags   |
+-------------------+----------+--------------------------------+

Think of x-mcp-description as writing instructions specifically for the AI. Your OpenAPI summary might say “Get user by ID” (fine for docs), but the MCP description can add context that helps the LLM make smarter decisions about when and how to use the tool.

Authentication Built In

Most real APIs need auth. We’ve got you covered:

Bearer token:

export GITHUB_TOKEN="ghp_..."
mcp-server-openapi 
  --spec ./github-api.yaml 
  --auth-type bearer 
  --auth-token-env GITHUB_TOKEN

API key (header or query):

export MY_KEY="sk_..."
mcp-server-openapi 
  --spec ./api.yaml 
  --auth-type api-key 
  --auth-key-env MY_KEY 
  --auth-key-name X-API-Key 
  --auth-key-in header

Credentials stay in environment variables — never in config files or CLI args. 🔒

Error Handling That Makes Sense

When things go wrong (and they will), error mapping is clear:

+----------------+---------------------------------------------+
| What Happened  | What the MCP Client Sees                    |
+----------------+---------------------------------------------+
| 2xx response   | The actual response body (success!)         |
| 400-499 error  | Error with status code + response body      |
| 500-599 error  | "Upstream server error" + status code       |
| Timeout        | "Request timed out"                         |
| Can't connect  | "Failed to connect to upstream"             |
+----------------+---------------------------------------------+

All errors include enough context for the AI to understand what went wrong and communicate it to the user. No mysterious failures.

Docker Support

If you prefer containers:

docker run --rm -i 
  -v $(pwd)/specs:/specs 
  mcp-server-openapi --spec /specs/my-api.yaml

Works great for teams who want a standardized MCP setup across environments.

Why I Built This

Honestly? Laziness. The good kind.

I was working on a project where I needed my AI tools to interact with about a dozen internal APIs. Writing a custom MCP server for each one felt insane. The specs were already there. The parameter types were already documented. The auth was already defined.

I just needed something to connect the dots.

Now, adding a new API to my MCP setup takes me less than a minute: tag the endpoints, point the server at the spec, done.

What’s Coming Next

The project is MIT-licensed and open for contributions. Here’s what’s on the roadmap:

  • 🔐 OAuth2 client credentials flow
  • 📄 Config file support (YAML)
  • 🔄 SSE transport
  • ⚠️ x-mcp-confirm extension for destructive operations
  • 🔥 Spec hot-reload
  • ⏱️ Rate limiting and retry policies

If any of that sounds interesting, PRs are welcome!

Try It Out

go install github.com/soyvural/mcp-server-openapi/cmd/mcp-server-openapi@latest
mcp-server-openapi --spec examples/petstore/petstore.yaml

⭐ GitHub Repo

I’d love to hear what APIs you connect with this. Drop a comment or open an issue — always happy to chat.

Happy building! 🚀

Cross Cloud ADK with Amazon Fargate, and Gemini CLI

Leveraging the Google Agent Development Kit (ADK) and the underlying Gemini LLM to build low code apps with the Python programming language deployed to the Fargate service on AWS.

Aren’t There a Billion Python MCP Demos?

Yes there are.

Python has traditionally been the main coding language for ML and AI tools. The goal of this article is to provide a minimal viable basic working MCP stdio server that can be run locally without any unneeded extra code or extensions.

What Is Python?

Python is an interpreted language that allows for rapid development and testing and has deep libraries for working with ML and AI:

Welcome to Python.org

Python Version Management

One of the downsides of the wide deployment of Python has been managing the language versions across platforms and maintaining a supported version.

The pyenv tool enables deploying consistent versions of Python:

GitHub – pyenv/pyenv: Simple Python version management

As of writing — the mainstream python version is 3.13. To validate your current Python:

admin@ip-172-31-70-211:~/gemini-cli-aws/mcp-lightsail-python-aws$ python --version
Python 3.13.12

Amazon Fargate

AWS Fargate is a serverless, pay-as-you-go compute engine for containers that works with Amazon Elastic Container Service (ECS) or Elastic Kubernetes Service (EKS). It eliminates the need to manage, patch, or scale underlying EC2 virtual machines. Fargate automatically allocates, scales, and manages compute infrastructure, allowing developers to focus solely on designing and operating applications.

Details are here:

Serverless Compute – AWS Fargate – AWS

Gemini CLI

If not pre-installed you can download the Gemini CLI to interact with the source files and provide real-time assistance:

npm install -g @google/gemini-cli

Testing the Gemini CLI Environment

Once you have all the tools and the correct Node.js version in place- you can test the startup of Gemini CLI. You will need to authenticate with a Key or your Google Account:

gemini

admin@ip-172-31-70-211:~/gemini-cli-aws/mcp-lightsail-python-aws$ gemini

▝▜▄ Gemini CLI v0.33.1
    ▝▜▄
   ▗▟▀ Logged in with Google /auth
  ▝▀ Gemini Code Assist Standard /upgrade

? for shortcuts 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 shift+tab to accept edits 3 GEMINI.md files | 1 MCP server
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 > Type your message or @path/to/file
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 ~/.../mcp-lightsail-python-aws (main*) no sandbox (see /docs) /model Auto (Gemini 3) | 239.8 MB

Node Version Management

Gemini CLI needs a consistent, up to date version of Node. The nvm command can be used to get a standard Node environment:

GitHub – nvm-sh/nvm: Node Version Manager – POSIX-compliant bash script to manage multiple active node.js versions

Docker Version Management

The AWS Cli tools and Lightsail extensions need current version of Docker. If your environment does not provide a recent docker tool- the Docker Version Manager can be used to downlaod the latest supported Docker:

Install

AWS CLI

The AWS CLI provides a command line tool to directly access AWS services from your current environment. Full details on the CLI are available here:

Install Docker, AWS CLI, and the Lightsail Control plugin for containers

Agent Development Kit

The Google Agent Development Kit (ADK) is an open-source, Python-based framework designed to streamline the creation, deployment, and orchestration of sophisticated, multi-agent AI systems. It treats agent development like software engineering, offering modularity, state management, and built-in tools (like Google Search) to build autonomous agents.

The ADK can be installed from here:

Agent Development Kit (ADK)

This seems like a lot of Configuration!

Getting the key tools in place is the first step to working across Cloud environments. For a deeper dive- a project with a similar setup can be found here:

MCP Development with Amazon Fargate and Gemini CLI

Where do I start?

The strategy for starting low code agent development is a incremental step by step approach.

The agents in the demo are based on the original code lab:

Create and deploy low code ADK (Agent Deployment Kit) agents using ADK Visual Builder | Google Codelabs

First, the basic development environment is setup with the required system variables, and a working Gemini CLI configuration.

Then, a minimal ADK Agent is built with the visual builder. Next — the entire solution is deployed to Google Cloud Run.

Setup the Basic Environment

At this point you should have a working Python environment and a working Gemini CLI installation. The next step is to clone the GitHub samples repository with support scripts:

cd ~
git clone https://github.com/xbill9/gemini-cli-aws
cd adkui-fargate

Then run init.sh from the cloned directory.

The script will attempt to determine your shell environment and set the correct variables:

source init.sh

If your session times out or you need to re-authenticate- you can run the set_env.sh script to reset your environment variables:

source set_env.sh

Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.

Verify The ADK Installation

To verify the setup, run the ADK CLI locally with Agent1:

xbill@penguin:~/gemini-cli-aws/adkui-fargate$ adk run Agent1
Log setup complete: /tmp/agents_log/agent.20260322_155748.log
To access latest log: tail -F /tmp/agents_log/agent.latest.log
/home/xbill/.pyenv/versions/3.13.12/lib/python3.13/site-packages/google/adk/cli/utils/agent_loader.py:248: UserWarning: [EXPERIMENTAL] _load_from_yaml_config: This feature is experimental and may change or be removed in future versions without notice. It may introduce breaking changes at any time.
  if root_agent := self._load_from_yaml_config(actual_agent_name, agents_dir):
/home/xbill/.pyenv/versions/3.13.12/lib/python3.13/site-packages/google/adk/features/_feature_decorator.py:81: UserWarning: [EXPERIMENTAL] feature FeatureName.AGENT_CONFIG is enabled.
  check_feature_enabled()
/home/xbill/.pyenv/versions/3.13.12/lib/python3.13/site-packages/google/adk/cli/cli.py:204: UserWarning: [EXPERIMENTAL] InMemoryCredentialService: This feature is experimental and may change or be removed in future versions without notice. It may introduce breaking changes at any time.
  credential_service = InMemoryCredentialService()
/home/xbill/.pyenv/versions/3.13.12/lib/python3.13/site-packages/google/adk/auth/credential_service/in_memory_credential_service.py:33: UserWarning: [EXPERIMENTAL] BaseCredentialService: This feature is experimental and may change or be removed in future versions without notice. It may introduce breaking changes at any time.
  super(). __init__ ()
Running agent Agent1, type exit to exit.
[user]: what is the weather in ho ho kus
[Agent1]: In Ho Ho Kus, New Jersey, it is currently cloudy with a temperature of 64°F (18°C), and it feels like 64°F (18°C). There is a 0% chance of precipitation at the moment, and the humidity is around 62%.

For tonight, expect mostly cloudy skies with a slight chance of showers after midnight, and lows around 40°F. Northeast winds of 5 to 10 mph will become southeast after midnight, with a 20% chance of rain.

Tomorrow, Sunday, March 22, 2026, the forecast indicates mostly cloudy conditions with a slight chance of showers in the morning, followed by partly sunny skies in the afternoon. Highs will be in the upper 60s, with south winds at 5 to 10 mph and a 20% chance of rain.
[user]: Run the deploy version on the local system: 0.0s 0.0s

Deploying to Amazon Fargate

First authenticate:

aws login --remote

Then cache the credentials locally:

xbill@penguin:~/gemini-cli-aws/adkui-fargate$ source save-aws-creds.sh
Exporting AWS credentials...
Successfully saved credentials to .aws_creds
The Makefile will now automatically use these for deployments.
xbill@penguin:~/gemini-cli-aws/adkui-fargate$ 

Then start the deployment:

xbill@penguin:~/gemini-cli-aws/adkui-fargate$ make deploy
./save-aws-creds.sh
Exporting AWS credentials...
Successfully saved credentials to .aws_creds
The Makefile will now automatically use these for deployments.
./deploy-fargate.sh
Loading AWS credentials from .aws_creds...
Building Docker image...

You can validate the final result by checking the messages:


xbill@penguin:~/gemini-cli-aws/adkui-fargate$ make status
----------------------------------------------------------------------------------------------------------------------------------
| DescribeServices |
+---------+----------------------+----------+---------+--------------------------------------------------------------------------+
| Desired | Name | Running | Status | TaskDef |
+---------+----------------------+----------+---------+--------------------------------------------------------------------------+
| 0 | adk-fargate-service | 0 | ACTIVE | arn:aws:ecs:us-east-1:106059658660:task-definition/adk-fargate-task:4 |
+---------+----------------------+----------+---------+--------------------------------------------------------------------------+
xbill@penguin:~/gemini-cli-aws/adkui-fargate$ 

You can then get the endpoint:

xbill@penguin:~/gemini-cli-aws/adkui-fargate$ make endpoint
Fargate Endpoint: http://100.54.104.31:8080

The service will be visible in the AWS Fargate console:

https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/adk-fargate-cluster/services?region=us-east-1

The console will look similar to:

Running the ADK Web Interface

Start a connection to the Fargate Deployed ADK:

https://us-east-1.console.aws.amazon.com/ecs/v2/clusters/adk-fargate-cluster/services?region=us-east-1

This will bring up the ADK UI. Select the sub-agent “Agent1”:

Run a Second Sub Agent

Connect to the ADK and select “Agent2”. Give the Agent this prompt:

Create an image of a cat.

The sub agent will then generate the cat image:

Visual Build an Agent

To use the ADK visual builder- select the pencil Icon next to Agent 2. You can drill down into the Agent design:

Summary

The Agent Development Kit was used to visually define a basic agent and added the Google Search Tool. This Agent was tested locally with the CLI and then with the ADK web tool. Then, several sample ADK agents were run directly from the Fargate deployment in AWS. This approach validates that cross cloud tools can be used — even with more complex agents.

How to Actually Migrate from GitHub to Codeberg Without Losing Your Mind

So you’ve finally decided to move some repos off GitHub. Maybe it’s the AI training concerns, maybe it’s the principle of using a platform run by a nonprofit, or maybe you just like the idea of Forgejo. Whatever the reason, you’ve landed on Codeberg — and now you’re staring at dozens of repositories wondering how to do this without mass-breaking everything.

I’ve been there. I migrated about 15 repos last month, and honestly? The actual migration is the easy part. It’s everything around the migration that trips you up.

The Real Problem: It’s Not the Git Part

Let’s be clear about something. Moving a git repository from one remote to another is trivial. You’ve probably done it a hundred times:

git remote set-url origin https://codeberg.org/yourname/yourrepo.git
git push --all
git push --tags

That’s it for the code. But your repo isn’t just code, is it? It’s issues, pull requests, CI pipelines, badges in your README, links other people have bookmarked, and contributor workflows that assume GitHub. That’s where the pain lives.

Step 1: Use Codeberg’s Built-In Migration Tool

Codeberg runs on Forgejo (a community fork of Gitea), and it has a surprisingly solid migration feature built right in. Go to the “+” menu in the top right, select “New Migration,” and paste your GitHub repo URL.

Here’s what it can pull over:

  • All branches and tags (obviously)
  • Issues and their comments
  • Labels and milestones
  • Pull requests (as issues, since the branches may not apply cleanly)
  • Releases with attached assets

The important thing is to check all the boxes during migration. I skipped the issues checkbox on my first repo thinking I’d do it manually later. I did not do it manually later. Don’t be me.

# If you prefer the API over the web UI, you can script it:
curl -X POST "https://codeberg.org/api/v1/repos/migrate" 
  -H "Authorization: token YOUR_CODEBERG_TOKEN" 
  -H "Content-Type: application/json" 
  -d '{
    "clone_addr": "https://github.com/yourname/yourrepo.git",
    "repo_name": "yourrepo",
    "repo_owner": "yourname",
    "service": "github",
    "auth_token": "YOUR_GITHUB_TOKEN",
    "mirror": false,
    "issues": true,
    "labels": true,
    "milestones": true,
    "releases": true,
    "pull_requests": true
  }'

The auth_token for GitHub is needed if you’re migrating private repos or want to pull over issues. A fine-grained personal access token with read-only permissions works fine.

Step 2: The Lazy Redirect Strategy

Here’s the thing most migration guides skip: you don’t have to go all-in on day one. The lazy approach is to keep your GitHub repo around as a mirror that redirects people to Codeberg.

Update your GitHub repo description to something like:

⚠️ This repo has moved to https://codeberg.org/yourname/yourrepo

Then update your README:

## This project has moved

This repository is now maintained at
[codeberg.org/yourname/yourrepo](https://codeberg.org/yourname/yourrepo).

This GitHub mirror may be out of date. Please file issues and
contributions on Codeberg.

Archive the GitHub repo (Settings → Danger Zone → Archive) so it becomes read-only. This way existing links don’t 404, stars and forks stay visible, and you’re not maintaining two active copies.

Step 3: Fix Your CI (This Is Where It Actually Hurts)

If you were using GitHub Actions, that’s the one thing that absolutely will not follow you. Codeberg supports Woodpecker CI, which is a community-maintained fork of Drone. The syntax is different but the concepts map pretty directly.

Here’s a GitHub Actions workflow vs the Woodpecker equivalent:

# .woodpecker.yml — equivalent of a basic CI build
steps:
  - name: test
    image: node:20-alpine
    commands:
      - npm ci
      - npm test

  - name: lint
    image: node:20-alpine
    commands:
      - npm ci
      - npm run lint

It’s simpler than GitHub Actions, which is both a blessing and a curse. No marketplace of prebuilt actions, but also no YAML spaghetti with uses: some-random-person/action@v3 and fingers crossed they haven’t been compromised.

You’ll need to enable CI for your repo at ci.codeberg.org. Sign in with your Codeberg account and activate the repo. Woodpecker picks up .woodpecker.yml from your repo root automatically.

Step 4: Batch Migrate Like a Reasonable Person

If you have more than a handful of repos, do NOT migrate them all at once. Seriously. I made a simple script that migrates repos one at a time with a delay, so I could sanity-check each one:

#!/bin/bash
# migrate-repos.sh — migrate a list of repos from GitHub to Codeberg

CODEBERG_TOKEN="your_codeberg_token"
GITHUB_TOKEN="your_github_token"
CODEBERG_USER="yourname"
GITHUB_USER="yourname"

# list your repos, one per line
REPOS=(
  "project-alpha"
  "dotfiles"
  "cool-library"
)

for repo in "${REPOS[@]}"; do
  echo "Migrating $repo..."
  curl -s -X POST "https://codeberg.org/api/v1/repos/migrate" 
    -H "Authorization: token $CODEBERG_TOKEN" 
    -H "Content-Type: application/json" 
    -d "{
      "clone_addr": "https://github.com/$GITHUB_USER/$repo.git",
      "repo_name": "$repo",
      "repo_owner": "$CODEBERG_USER",
      "service": "github",
      "auth_token": "$GITHUB_TOKEN",
      "mirror": false,
      "issues": true,
      "labels": true,
      "releases": true
    }"
  echo "Done. Check https://codeberg.org/$CODEBERG_USER/$repo"
  sleep 5  # be nice to the API
done

I migrated 3-4 repos per day, verified each one, and moved on. Took about a week total for 15 repos but nothing broke.

What You’ll Actually Miss

I want to be honest about the tradeoffs because pretending Codeberg is a perfect replacement helps nobody:

  • GitHub Actions ecosystem — Woodpecker works but you’ll rewrite CI configs and lose access to the massive actions marketplace
  • Discoverability — Nobody is browsing Codeberg to find new projects. If your project depends on drive-by contributors, this matters
  • GitHub Pages — Codeberg has Codeberg Pages, but the setup is different. You’ll need a separate branch or repo
  • Dependency graph / Dependabot — Doesn’t exist on Codeberg. You’ll want to set up Renovate Bot or similar yourself
  • Network effects — Your contributors need Codeberg accounts. Some won’t bother

On the flip side, Codeberg is fast, the Forgejo UI is clean and actively improving, and there’s something genuinely nice about using a platform that’s run by a nonprofit in the EU with no VC money involved.

Prevention: Set Yourself Up So the Next Migration Is Easier

The real lesson from any platform migration is to reduce lock-in going forward:

  • Keep CI config minimal — The less platform-specific magic you use, the easier the next move. Prefer running shell scripts from CI rather than relying on platform-specific action plugins
  • Use generic git features — Issues and PRs are the main lock-in. Consider using a CONTRIBUTING.md that works anywhere
  • Mirror from the start — You can push to multiple remotes trivially. Add this to your git config and you’re always one step ahead:
# Push to both GitHub and Codeberg simultaneously
git remote set-url --add --push origin https://codeberg.org/yourname/repo.git
git remote set-url --add --push origin https://github.com/yourname/repo.git

Now every git push goes to both. When you’re ready to drop GitHub, just remove that URL.

The Bottom Line

Migrating to Codeberg isn’t hard. It’s just tedious if you have a lot of repos with active issues and CI. The lazy strategy — migrate with the built-in tool, redirect GitHub repos, fix CI gradually — works well and doesn’t require a heroic weekend of DevOps.

Start with one or two less-critical repos. Get comfortable with the workflow. Then move the rest at your own pace. The repos aren’t going anywhere.

A Real Time Information Using Andorra’s Government APIs

Duty pharmacies map

For those who don’t know it, Andorra is a tiny country of 80k people located in the Pyrenees. I’ve been living here for 3 years and built a guide for people moving here.

The downside of living here? Snowstorms and traffic jams. Andorra only has two road entrances, so when there’s heavy snow or a holiday weekend, it’s chaos. You need to know which roads are clear, whether snowplows are out, and if there’s an alternative route.

Turns out the Andorran government has quite good public APIs. No keys or auth, so I used them to make my life easier.

Architecture

Built this with Next.js on Vercel. Each API route caches for 3 minutes so I don’t overload the government APIs:

let cache = null;
const CACHE_TTL = 3 * 60 * 1000;

export async function GET() {
  if (cache && Date.now() - cache.timestamp < CACHE_TTL) {
    return NextResponse.json(cache);
  }

  const data = await fetch(`${MOBILITAT_BASE}/cameras`).then(r => r.json());
  cache = { ...data, timestamp: Date.now() };
  return NextResponse.json(cache);
}

For OpenStreetMap data (supermarkets, banks, pharmacies…) the API has aggressive rate limiting, so I scrape 3 categories per run via GitHub Actions and store in Upstash Redis, so I don’t need to pay for a server.

Duty pharmacies map

A small issue is that Pharmacy shifts change at 9:30, not midnight, so if someone checks at 8AM, yesterday’s pharmacy is still displayed for now.

The endpoints

These are the endpoints I used:

Mobilitat (that, is, traffic)

app.mobilitat.ad/api/v1/ gives you:

  • GET /cameras: Dozens oflive traffic cameras (animated GIFs!)
  • GET /incidents: Active road incidents
  • GET /llevaneus: Snowplows with real-time GPS + speed
  • GET /nevadesactive: Snow alert active? true/false

The snowplow endpoint returns the exact position, heading and speed of every active snowplow:

{
  "latitude": 42.5436866,
  "longitude": 1.733995,
  "heading": 22,
  "speed": 19
}

So now I can check if the snowplows are out before leaving home in the morning, since I live at 2000 meters and some days the road is impassable.

The camera endpoints return GIFs and are quite heavy.

Traffic cameras

Fuel Prices

All 60 gas stations with official prices. Gas here is cheap (1.53 EUR/L) because Andorra’s VAT is only 4.5%:

{
  "name": "BP Whatever",
  "parish": "Canillo",
  "fuels": [{ "fuel_name": "Gas 95", "price": 1.535 }]
}

Ski Weather

Free, no API key. You need to pass the resort’s elevation to get accurate mountain weather:

GET https://api.open-meteo.com/v1/forecast
  ?latitude=42.57&longitude=1.66&elevation=1800
  &current=temperature_2m,snow_depth,wind_speed_10m

The tools

These are the tools I built:

Thanks for reading!

How I trained a computer vision model on the AWS Free Tier

App detecting 4 Easter eggs in an astronaut scene, with bounding boxes and confidence scores from 52% to 66.8%

Last updated: March 26, 2026

Developers hide Easter eggs in code. Filmmakers hide them in scenes. I hid them in AI-generated images and asked Amazon Rekognition to find them.

If you’ve ever wondered whether AWS Free Tier gives you enough credits to build something cool, this post is for you. I built an Easter egg detector powered by Amazon Rekognition, tested its standard object detection API on 37 images, and trained a custom model, all with my AWS Free Tier credits.

(If you haven’t set up a Free Tier account, check out this great guide!)

The idea

I wanted to build a simple and fun app where I can upload a photo and have the app tell me if there’s an Easter egg in it. It sounds kind of silly, but the technical problem it maps to — locating a small, specific object in a complex scene — is a foundational one for the field of computer vision.

AWS has a lot of services for different AI/ML use cases, including Amazon Rekognition for computer vision. I learned that organizations like C-SPAN and the NFL use it to understand what’s in their images and video. And Amazon Rekognition is available on the AWS Free Tier, which makes experimenting with it easier.

The service gives you different ways to figure out what’s in images. The DetectLabels API works out of the box and recognizes thousands of common objects and concepts, everything from aircraft to zucchini to birthday parties. Custom Labels let you train your own computer vision model when the built-in labels fall short. With this project, I wanted to find out where the first one stops being useful and the other is needed.

What I built

The app I built to explore that question is a Streamlit app with two modes. Standard mode sends your image to the DetectLabels API and checks if it returns “Egg” or “Easter Egg” in the labels. Custom Labels mode uses a custom model I trained on my own images. Both draw bounding boxes around any eggs they find.

App detecting 4 Easter eggs in an astronaut scene, with bounding boxes and confidence scores from 52% to 66.8%

The application code is about 200 lines of Python, 100 lines of which is UI-related, and you can check it out at this GitHub repo. You upload an image, the app calls Rekognition using the AWS SDK for Python (Boto3), checks the response, and draws some boxes around any eggs that it finds.

Here’s an example of code that handles regular DetectLabels requests.

response = rekognition.detect_labels(
    Image={"Bytes": image_bytes},
    MaxLabels=30,
    MinConfidence=30.0,
)

Generating test images

I needed images with scenes where Easter eggs would be unexpected. I used an image generator to create 19 images: a messy desk with an egg tucked next to the monitor, a toy box with a painted Easter egg between two teddy bears, a picnic spread with a couple eggs in between the sandwiches.

AI-generated picnic scene with colorful Easter eggs hidden among sandwiches, chips, and lemonade on a blanket

Then I generated 18 images with no eggs at all, but similar to the other scenes. That gave me a relatively balanced dataset to start with.

Test image: sunny picnic scene with sandwiches, chips, and a straw hat but no hidden Easter eggs

Round 1: DetectLabels

I wrote a batch test script (also in the GitHub repo) that ran every image I had through DetectLabels and checked whether Rekognition found an egg.

The results at a 30% confidence threshold:

Metric Score
Accuracy 75.70%
Precision 100%
Recall 52.60%

100% precision means it never hallucinated an egg where there wasn’t one in the no-egg images. But 52.6% recall means it missed about half the eggs in the egg images. Imagine you’re doing an Easter egg hunt, and there are 19 eggs hidden in the yard. A recall of 52.6% means you only found 10 of them.

At a stricter 70% confidence threshold, recall dropped to 36.8%, so you’d only find about 7 out of 19.

I added logging to see what objects Rekognition detected for the missed eggs. The logged objects were pretty accurate, which told me that Rekognition identified the gist of the images well but couldn’t “see” the Easter eggs.

I realized the out-of-the-box API was great for detecting Easter eggs in more typical scenes, like this one.

Colorful painted Easter eggs nestled in green grass on a sunny day

Script output:

  ✅ 1774389521556_img.png eggs: Egg (99.9%), Easter Egg (99.1%)

DetectLabels had a harder time finding Easter eggs in less obvious scenarios or with more competing objects, like this one.

A painted Easter egg hidden among teddy bears, crayons, and building blocks in a toy box

Script output:

❌ MISS 1774389716587_img.png eggs: none
↳ saw instead: Toy (98.7%), Teddy Bear (93.5%), Lego Set (69.8%), 
   Plush (53.7%), Doll (48.1%), Play Area (48.0%), Handicraft (47.7%), 
   Hedgehog (46.9%)

Round 2: Custom Labels

Custom Labels exist for situations where you need to detect specific objects in specific scenes that go beyond the capabilities of DetectLabels, like my Easter egg images. With Custom Labels, you can train a computer vision model on your own images. The model can learn to spot what you care about, even when the object is small, partially hidden, or surrounded by lots of visual noise. The benefit here is that rather than training a computer vision model from scratch on tens of thousands of images, you can build on existing capabilities by just uploading a relatively small set of training images. Then, Rekognition handles the model training and evaluation process for you.

I did this all directly in the Amazon Rekognition console, where I created a new project and uploaded my dataset of images. Rekognition automatically split my data 80/20 for training and testing.

Amazon Rekognition Custom Labels console showing the dataset page with 37 uploaded training images

I created my custom label of “Easter egg” and drew bounding boxes around the Easter eggs in my dataset images, then hit Train.

Custom Labels console labeling view with bounding boxes drawn around Easter eggs on a cluttered desk scene

Training took around 30 minutes. When it finished, I got a model ARN and performance metrics. Here’s how the custom model did on the test dataset compared to the standard DetectLabels API:

Metric DetectLabels (30% threshold) Custom Labels
Precision 100% 77.80%
Recall 52.60% 75%
F1 score (not calculated) 0.764

Recall jumped from 52.6% to 75%. This meant that the custom model was finding eggs that DetectLabels missed entirely. Precision dropped from 100% to 77.8% (more false positives), meaning it occasionally saw an egg where there wasn’t one, but I think that’s a reasonable trade-off for catching 22% more of the eggs.

The F1 score is a metric that shows how well the model balances precision and recall, indicating its overall accuracy. A score of 0.764 from a dataset of 37 images is a good starting point.

Going back to the Easter egg hunt analogy, DetectLabels was the careful egg hunter who only picked up real eggs but walked past half of them. The custom label found most of the eggs but also grabbed a few eggish-looking rocks along the way in the test dataset.

Considering the fact that I only had 37 total images in the dataset, these are promising results, but there’s definitely room to improve. I’d expect the model’s performance to climb if I added more training data. But this experiment was for learning and my own curiosity, and that F1 score worked for me. I plugged my custom model’s ARN into my Streamlit app, and uploaded a few images. The custom model did a pretty good job of detecting Easter eggs on new images that weren’t part of the training dataset.

Egg or Not app in Custom Labels mode detecting one Easter egg in a grocery store aisle at 75% confidence

It also was pretty good about not registering false positives, even with temptingly bright round objects in the image.

Egg or Not app correctly reporting no eggs found in a grocery store produce aisle image

Cost

Everything in this project fits comfortably within the AWS Free Tier credits for new accounts. Here are some more details on the pricing for training and inferencing with a custom model in the us-east-1 region:

DetectLabels (Group 2 API)

  • 74 images × $0.001/image = $0.074
    • I had 37 images, but I ran the batch analysis script twice (first checking for egg presence, then again to log the labels that were found for false negatives).

Custom Labels training:

  • 0.52 hours × $1/hour = $0.52
    • The docs note that Amazon Rekognition may run multiple compute resources in parallel, so actual billed hours can be higher than elapsed time for model training.

Custom Labels inferencing:

  • 1.41 hours x $4/hour = $5.71

If you build this in your own account, be sure to STOP the model when you’re done inferencing to avoid accumulating further charges. In total for this project, I was billed for 85.7 inference minutes and 31 training minutes, which amounted to $6.24. This amount was deducted from my Free Tier credits. (The charges for other services in the following cost breakdown image are related to other Free Tier projects.)

AWS Cost Explorer showing March 2026 breakdown: Rekognition $6.24, EC2 $0.27, VPC $0.13, S3 $0.01

What I learned, and what I’d do differently

The jump from DetectLabels to Custom Labels was the most interesting part of this project for me. With Custom Labels, you get to label your own data, train a model, evaluate its performance, and iterate. That loop (data → train → evaluate → improve) is the same loop behind every ML system. Custom Labels just makes it accessible without needing to have ML experience or having to spin up your own dedicated hardware.

I also came away with a better sense of when to use what. You could send an image to one of the newer multimodal foundation models and ask, “Are there any Easter eggs in this picture?” It would probably give you a very good answer. But for a tightly-scoped detection task like this one, I think a purpose-built service is a better fit. A single API call returns predictable JSON with labels, confidence scores, and bounding box coordinates. No system prompt to write, no natural language to parse. And the whole project cost me $6.24 in Free Tier credits.

A couple things I’d change if I were starting over:

  • Create more images for the dataset. The docs say that most custom models need a few hundred images or less, but I only had 37. I’d aim for 50+ per class (Easter egg and no Easter egg). I’d also set up a script to generate text for image prompts and call an image generating API to quickly create a bunch of training and test images.
  • Test with real photos, not just AI-generated ones. Generated images have a certain look to them. Real phone photos with unexpected lighting and odd angles would be a tougher test.

Try it yourself

The full code and installation directions are on GitHub here.

If you want to skip the training and just play with DetectLabels, the standard mode works immediately. Upload a photo of your breakfast and see what happens. 🍳

If you want more ideas for Free Tier projects, let me know in the comments!

Building a Media Monitoring Tool: Mentions Across News Sites

Media monitoring, tracking brand mentions, competitor coverage, and industry news, is a service companies pay thousands for monthly. Here’s how to build your own with Python and web scraping.

What We’ll Build

A tool that:

  • Monitors multiple news sources for keyword mentions
  • Extracts article metadata (title, date, author, snippet)
  • Deduplicates across sources
  • Sends alerts for new mentions

Setup

pip install requests beautifulsoup4 pandas feedparser newspaper3k

RSS Feed Monitor

The fastest approach starts with RSS feeds, as many news sites still publish them:

import feedparser
from datetime import datetime

class RSSMonitor:
    def __init__(self, keywords):
        self.keywords = [k.lower() for k in keywords]
        self.seen_urls = set()

    def check_feed(self, feed_url, source_name):
        """Check an RSS feed for keyword mentions."""
        feed = feedparser.parse(feed_url)
        mentions = []

        for entry in feed.entries:
            title = entry.get("title", "")
            summary = entry.get("summary", "")
            link = entry.get("link", "")

            if link in self.seen_urls:
                continue

            text = f"{title} {summary}".lower()
            matched = [k for k in self.keywords if k in text]

            if matched:
                self.seen_urls.add(link)
                mentions.append({
                    "source": source_name,
                    "title": title,
                    "url": link,
                    "summary": summary[:300],
                    "keywords_matched": matched,
                    "published": entry.get("published", ""),
                    "found_at": datetime.now().isoformat()
                })

        return mentions

Web Scraping for Sites Without RSS

import requests
from bs4 import BeautifulSoup

SCRAPER_API_KEY = "YOUR_KEY"

class WebMonitor:
    def __init__(self, keywords):
        self.keywords = [k.lower() for k in keywords]
        self.seen_urls = set()

    def scrape_news_site(self, base_url, source_name, article_selector, title_selector):
        """Scrape a news site for articles matching keywords."""
        api_url = (
            f"http://api.scraperapi.com?api_key={SCRAPER_API_KEY}"
            f"&url={base_url}"
        )
        response = requests.get(api_url, timeout=30)
        soup = BeautifulSoup(response.text, "html.parser")

        mentions = []
        for article in soup.select(article_selector):
            title_el = article.select_one(title_selector)
            link_el = article.select_one("a[href]")

            if not title_el or not link_el:
                continue

            title = title_el.text.strip()
            url = link_el["href"]
            if not url.startswith("http"):
                url = base_url.rstrip("/") + url

            if url in self.seen_urls:
                continue

            matched = [k for k in self.keywords if k in title.lower()]

            if matched:
                self.seen_urls.add(url)
                mentions.append({
                    "source": source_name,
                    "title": title,
                    "url": url,
                    "keywords_matched": matched,
                    "found_at": datetime.now().isoformat()
                })

        return mentions

Full Article Extraction

from newspaper import Article

def extract_full_article(url):
    """Extract full article text and metadata."""
    try:
        article = Article(url)
        article.download()
        article.parse()

        return {
            "title": article.title,
            "authors": article.authors,
            "publish_date": str(article.publish_date) if article.publish_date else "",
            "text": article.text,
            "top_image": article.top_image,
            "word_count": len(article.text.split())
        }
    except Exception as e:
        return {"error": str(e)}

The Monitoring Pipeline

import time
import json

NEWS_FEEDS = [
    ("TechCrunch", "https://techcrunch.com/feed/"),
    ("The Verge", "https://www.theverge.com/rss/index.xml"),
    ("Ars Technica", "https://feeds.arstechnica.com/arstechnica/index"),
]

NEWS_SITES = [
    ("Reuters Tech", "https://www.reuters.com/technology/", "article", "h3"),
]

def run_monitoring(keywords, interval_minutes=30):
    rss = RSSMonitor(keywords)
    web = WebMonitor(keywords)
    all_mentions = []

    while True:
        print(f"n[{datetime.now()}] Checking sources...")

        for name, url in NEWS_FEEDS:
            mentions = rss.check_feed(url, name)
            all_mentions.extend(mentions)
            for m in mentions:
                print(f"  NEW: [{m['source']}] {m['title']}")

        for name, url, art_sel, title_sel in NEWS_SITES:
            try:
                mentions = web.scrape_news_site(url, name, art_sel, title_sel)
                all_mentions.extend(mentions)
                for m in mentions:
                    print(f"  NEW: [{m['source']}] {m['title']}")
            except Exception as e:
                print(f"  Error with {name}: {e}")
            time.sleep(3)

        with open("mentions.json", "w") as f:
            json.dump(all_mentions, f, indent=2)

        print(f"Total mentions: {len(all_mentions)}")
        time.sleep(interval_minutes * 60)

# Run it
run_monitoring(["openai", "anthropic", "google ai", "llm"])

Scaling Considerations

For production media monitoring, ScraperAPI handles proxy rotation across news sites reliably. ThorData residential proxies help with geo-restricted content. Track your scraper health metrics with ScrapeOps.

Conclusion

A custom media monitoring tool gives you real-time awareness without expensive SaaS subscriptions. Start with RSS feeds for speed, add web scraping for coverage, and build alerts from there.

AI Weekly Digest: Mar 20-27, 2026 — TurboQuant Shakes Memory Markets, MCP Hits 97M, Manus Founders Barred from Leaving China

This Week in AI (March 20–27, 2026)

A week of infrastructure-shaking research, geopolitical drama, and record-breaking funding. Here are the stories that matter.

🔬 Research & Breakthroughs

Google TurboQuant — The Compression Breakthrough That Crashed Memory Stocks

Google unveiled TurboQuant (to be presented at ICLR 2026), a compression algorithm that reduces LLM memory requirements by 6x and achieves up to 8x faster attention by quantizing key-value caches to just 3 bits — with zero accuracy loss.

The announcement immediately impacted markets: Micron, Samsung, and Western Digital stock prices dropped as investors reassessed demand forecasts for AI memory chips. If models need 6x less memory, do we really need all those HBM fabs?

Think of it as the “Pied Piper moment” for AI infrastructure — suddenly, the bottleneck shifted.

“Societies of Thought” — Published in Science

A fascinating paper published in Science revealed that when reinforcement learning rewards models solely for reasoning accuracy, they spontaneously develop multi-perspective, conversational reasoning behaviors.

The implication: robust reasoning doesn’t need to be explicitly programmed — it emerges as a social process through optimization pressure alone. Models essentially learn to debate with themselves.

🛠 Infrastructure & Protocols

MCP Crosses 97 Million Installs

The Model Context Protocol (MCP) hit 97 million monthly SDK downloads (Python + TypeScript combined), now adopted by every major AI provider: Anthropic, OpenAI, Google, Microsoft, Amazon, xAI, Mistral, and Cohere.

What started as Anthropic’s experimental protocol has become the de facto standard for AI tool integration. The transition from “interesting experiment” to “foundational infrastructure” happened faster than anyone predicted.

💰 Industry & Funding

Harvey AI Raises $200M at $11B Valuation

Legal AI startup Harvey raised $200 million co-led by GIC and Sequoia, reaching an $11 billion valuation — nearly quadrupling from $3B just one year ago. Over 100,000 lawyers across 1,300 organizations now use the platform, with ARR at $190 million.

The legal industry, once considered slow to adopt technology, is now one of AI’s fastest-growing verticals.

AI Startups Now Consume 41% of All Venture Capital

AI startups accounted for 41% of the $128 billion in venture dollars raised on Carta in the past year — a record-high annual share. March 2026 alone has already produced more $100M+ AI funding rounds than any comparable period in venture history.

The question is no longer “should we invest in AI?” but “is there anything left to invest in that isn’t AI?”

🔧 Chips & Hardware

Huawei Ascend 950PR — China’s Answer to NVIDIA

Huawei launched the Ascend 950PR chip with the Atlas 350 accelerator card, claiming 2.8x the compute power of NVIDIA’s H20. The chip features:

  • Huawei-designed HBM (in-house, not Samsung/SK Hynix)
  • 128GB memory
  • 1.6 TB/s bandwidth

This is significant: Huawei is building a completely self-sufficient AI chip stack, from design to memory, independent of US-allied supply chains.

Samsung HBM4E Unveiled at GTC 2026

Samsung unveiled its seventh-generation HBM4E memory at NVIDIA GTC:

  • 4.0 TB/s bandwidth at 16 Gbps per pin
  • HBM4 already shipping since February
  • Pledging to triple HBM production capacity

The HBM arms race between Samsung and SK Hynix continues to intensify, with SK Hynix holding roughly two-thirds of NVIDIA’s 2026 HBM4 allocation.

🌏 China & Geopolitics

DeepSeek Pivots to Agentic AI

DeepSeek V4 remains unreleased despite months of anticipation, but the company is clearly shifting strategy. A cluster of new job openings for agentic AI specialists signals a pivot toward autonomous task-execution technology.

The question: is V4 delayed because they’re rethinking the entire approach, or because Huawei Ascend optimization is harder than expected?

Manus AI Founders Barred from Leaving China

In one of the most dramatic stories of the week, Chinese authorities barred Manus AI co-founders Xiao Hong and Ji Yichao from leaving the country amid a review of Meta’s $2 billion acquisition.

Regulators are examining whether core AI agent IP was transferred to Singapore without required government approvals. This case could set a precedent for how China handles AI company acquisitions by US tech giants.

ByteDance Leads China’s AI Race

ByteDance’s dominance in China’s AI landscape is staggering:

  • Doubao chatbot: 227 million monthly active users
  • 2026 AI budget: 160 billion yuan ($23 billion)
  • In talks with Samsung to manufacture 100,000 custom AI chips

For context, ByteDance’s AI budget alone exceeds the GDP of many small countries.

📜 Policy & Regulation

White House National AI Policy Framework

The White House released a comprehensive National Policy Framework for Artificial Intelligence with seven pillars:

  1. Child protection
  2. Community safety
  3. Intellectual property
  4. Free speech
  5. Innovation
  6. Workforce development
  7. Federal preemption of state laws

Key takeaways:

  • No new federal AI regulatory body — existing sector-specific regulators will handle AI
  • Limits developer liability for third-party misuse
  • Preempts state-level AI laws — significant given that 78 AI bills are active across 27 US states

This is the clearest signal yet of the current administration’s light-touch regulatory philosophy for AI.

📊 Week at a Glance

Category Headline Impact
Research Google TurboQuant 6x memory reduction, memory stocks drop
Research Societies of Thought Reasoning emerges as social process
Infrastructure MCP hits 97M installs Protocol becomes industry standard
Funding Harvey AI $200M raise Legal AI valued at $11B
Funding AI = 41% of all VC Record concentration of capital
Hardware Huawei Ascend 950PR 2.8x H20, fully self-sufficient stack
Hardware Samsung HBM4E 4.0 TB/s, tripling production
China DeepSeek → Agentic AI V4 delayed, strategy shift
China Manus founders detained Meta $2B deal under review
China ByteDance dominance $23B AI budget, 227M MAU
Policy White House AI Framework Light-touch, no new regulator

What caught your eye this week? Drop a comment below.

🤖 This digest is compiled using AI-assisted research and human editorial judgment. Published weekly every Friday.