Steps to Deploying a Virtual Machine in Linux

linux virtual machine

A virtual machine is a mini computer inside another computer in the cloud. Now we are deploying this virtual machine in an operating system called Linux. Linux is a free, open sourced operating system family used on computers, servers, smartphones and even supercomputers. Let me give you a hint, the very popular Android is built on the Linux Kernel.
Okay that’s enough intro for now, let’s get started.

Step 1: Login in to the[(https://portal.azure.com/#home)]

Step 2: Look up for virtual machine on the search bar just atop and start creating your Linux virtual machine.
linux virtual machine

select the +create icon which enables you to create a new VM
selecting +create

Step 3:I am assuming this is your first time creating a Linux virtual machine, it is best to create a new “resource group” as seen in the image below , one might wonder what a resource group is?. A resource group is the container that will be holding the virtual machine, so in layman terms the resource group is just like the “pot” and the Linux virtual machine is what we are cooking inside this pot.Resource group creation
so you create a new resource group, in my case I create one called “mylinuxvirtualmachinegroup” and I selected it, so create yours and select it.
Step 4: Configuring instance details
a: create a name for your VM that suits you
b: select a location that befits you
c: I chose the first option for the zone options, you can use the drop down and choose ‘no infrastructure redundancy” required if you are on a free trial account. _
_ d: for security type; I used the Trusted launch virtual machine as this is basic security for Linux VM.

e: for Image; Ubuntu is encouraged as that is the operating system Linux works on so everything rhymes.
Configuring instance details

Step 5: Login method and Network access configuration

  • Secure Safe Shell SSH Key (More Secure)
  • Password (Simpler for first timers)
    The choice is yours, personally I would use password as its easier and not so demanding.
    Create a username (like azureadmin)
    Create a strong password (write it down!)
    Public inbound ports: Select Allow selected ports
    Select inbound ports: Choose HTTP (80) and SSH (22)
    port selection
    scroll back to the monitoring tab to disable diagnostics after that click review and create
    disbabling


go to Resource to see, learn and understand everything about the Linux VM you just created

Step 6:Open the public IP address as shown

Resource
then click connect and click check access

Step 7: Now open Powershell on Windows or Terminal on Mac to connect to Linux with your IP address you just copied in image@9 above

click enter after pasting your IP address
after that a question pops up, are you sure you want to continue connecting? type in YES,
then the next prompt is to type in the password you used for your azure admin access, Linux wont show the normal asterisk ***** for password, don’t be restless its working in the background, this is a security measure to protect you, if your password is type correctly, hit enter and you would see the your IP address and port 22

type in sudo su to run command as root. Becoming a root user makes our commands run smoothly.
next step is to run apt update, (Advanced Package Tool) is a command-line utility for managing software on Debian-based Linux distributions, such as Ubuntu and Linux Mint, Kali Mint, etc. It handles installation, removal, upgrading, and dependency management of .deb packages by automating retrieval from online repositories. It also tells you the status of your Linux Virtual Machine and if everything is running very well.


next install nginx which is a high performance web server software used to host websites, serve web applications, act as reverse proxy, load balance traffic, and improve overall performance and security, nginx is one of the most widely used servers in the world.


when prompted, confirm with a Y
Although optional, you can also install Vim which is a text editor used in Linux and Unix systems which makes your layout more colorful, developers and system administrators use it to edit configuration, write code, manage servers and also modify scripts directly from terminal.
when prompted, confirm with a Y


Finally copy your IP address in image number 9 above.
load it on your browser and paste.

Voila, if you see the image above, you did everything correctly.

This is a beautiful introduction to Linux, the operating system that hosts most tech products you are using everyday like open AI, LinkedIN, YouTube etcetera.

Mr LINUX says welcome!

Stop Putting dd() Everywhere Debug the Database From the Source Instead

diagram

Every backend developer has done this at some point.

A request fails.

diagram

Something is not inserting into the database.

And suddenly the codebase turns into this:

dd($request->all());

dd($user);

dd($campaign);

Log::info($query);

You add logs in controllers.

Then services.

Then repositories.

Then queue workers.

Then event listeners.

And after two hours, you are still confused.

Because the real problem is not where you are looking.

The Day I Realized Controller Debugging Wasn’t Enough

A few months ago, a developer on a team I worked with faced a strange issue.

A simple form submission was supposed to:

  • create a user
  • assign a campaign
  • generate a lead
  • push a welcome email into queue
  • write an activity log

Pretty normal workflow.

But randomly, some users were being created without campaign data.

Sometimes the queue job worked.

Sometimes it didn’t.

Sometimes the transaction rolled back silently.

The first reaction?

Start adding logs everywhere.

Inside controllers:

Log::info('Controller hit');

Inside services:

Log::info('Campaign assigned');

Inside queue workers:

Log::info('Email queued');

Inside transactions:

DB::beginTransaction();

The code became a forest of debugging statements.

Still no clear answer.

The Real Problem

Here’s what most developers forget:

No matter how beautiful your architecture is…

No matter whether you use:

  • Laravel
  • Raw PHP
  • Node.js
  • Python
  • Java

At the end of the day, every query goes to one place:

MySQL.

Always.

Your application is just the messenger.

The database sees everything.

That realization changes how you debug systems.

Instead of Guessing, Watch the Database Directly

The moment we enabled MySQL General Query Log, everything became obvious.

We literally watched the queries execute live.

One terminal command showed the full story.

SET GLOBAL general_log = 'ON';

Then:

tail -f /var/lib/mysql/hostname.log

And suddenly we could see:

INSERT INTO users ...
UPDATE campaigns ...
ROLLBACK;

That last line explained everything.

The transaction was rolling back because another process locked a table for a few seconds.

No controller log would have shown the full picture that clearly.

This Is Why Framework-Level Debugging Often Fails

Modern applications are no longer simple.

A single request may involve:

  • queues
  • events
  • transactions
  • scheduled jobs
  • caching
  • background workers
  • third-party APIs

By the time you debug from inside the controller, the actual issue may already be happening somewhere else entirely.

This is why developers sometimes spend hours debugging “application logic” when the real problem is:

  • a deadlock
  • a lock wait
  • a slow query
  • missing index
  • rollback
  • transaction conflict

And MySQL already knows all of this.

The Most Useful MySQL Debugging Tools

Honestly, there are four MySQL features every backend developer should know.

Not just DBAs.

Actual application developers.

Because these tools solve real-world production headaches.

1. General Query Log See Everything

This is the closest thing to “watching the database think.”

Enable it:

SET GLOBAL general_log = 'ON';

Now every query gets logged.

You can literally submit a form and watch the database flow in real time.

This becomes incredibly useful when:

  • one request touches multiple tables
  • transactions fail
  • duplicate queries happen
  • hidden queries execute behind ORM magic

Especially in large Laravel applications where Eloquent sometimes hides too much.

2. Slow Query Log Find Hidden Performance Problems

This one saves applications.

Seriously.

Sometimes developers optimize controllers endlessly while the real problem is one terrible query.

Enable it:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

Now MySQL logs anything slower than one second.

This helps identify:

  • missing indexes
  • N+1 problems
  • huge scans
  • inefficient joins

I once saw a dashboard drop from 12 seconds to under 800ms just because a slow query log exposed a missing index.

The application code itself was completely fine.

3. InnoDB Status Understand Deadlocks & Locks

This command feels like magic the first time you use it.

SHOW ENGINE INNODB STATUS;

If your application ever experiences:

  • hanging requests
  • frozen queues
  • stuck inserts
  • transaction conflicts

This command can immediately reveal:

LATEST DETECTED DEADLOCK

or:

LOCK WAIT

And suddenly the mystery disappears.

4. Audit Tables & Triggers Permanent Visibility

This is where things become enterprise-level.

Sometimes you need permanent tracking.

For example:

  • who changed data
  • when it changed
  • which table changed
  • what action happened

Instead of depending on controller logs forever, you let MySQL automatically track important events.

Using triggers.

Example:

CREATE TRIGGER users_after_insert
AFTER INSERT ON users
FOR EACH ROW
INSERT INTO audit_logs(table_name, action_type)
VALUES ('users', 'INSERT');

Now the database tracks itself.

Clean.

Reliable.

Framework-independent.

One Important Thing Most Developers Ignore

Do not leave general_log permanently enabled in production.

That is a bad idea.

Because:

  • logs become massive
  • storage grows quickly
  • performance can suffer

The smarter workflow is:

Temporary Debugging

SET GLOBAL general_log = 'ON';

Investigate the issue.

Then disable it:

SET GLOBAL general_log = 'OFF';

Africa’s Digital Ecosystem is Not Dead

The Problem We Were Actually Solving

We were trying to build a platform that made it easy for African digital creators to sell their courses, ebooks, and music online. But our platform’s payment gateways were failing, resulting in frustrated users and lost sales. We thought our problem was with our users, but it turned out that the issue was with the payment providers.

What We Tried First (And Why It Failed)

We tried using PayPal’s “Borderless Payments” feature, thinking that it would solve our problem. However, we quickly realized that it was not enabled for our users’ countries. We then switched to Stripe, hoping that their more robust infrastructure would work. But Stripe’s “Risk and Compliance” team flagged our users’ transactions as “high-risk,” effectively blocking them. We tried to work around this by implementing additional verification checks, but it only made things worse.

The Architecture Decision

After weeks of debugging and trying different approaches, we finally decided to abandon the popular digital service providers and build our own payment processing system using Adyen and Paystack. Adyen’s global reach and Paystack’s expertise in African payment systems made it possible for us to process transactions from users in over 20 African countries. We also implemented a fallback system that used local payment methods like mobile money and bank transfers, ensuring that our users had a seamless payment experience.

What The Numbers Said After

After implementing our custom payment processing system, our transaction success rate improved by 80%. Our users were no longer facing payment failures, and our sales increased by 25%. We also saw a significant decrease in support tickets related to payment issues. The numbers were clear: our homegrown solution was working, and it was time to let go of the limitations of the popular digital service providers.

What I Would Do Differently

In hindsight, I would have invested more time upfront in researching the limitations of popular digital service providers. I would have also considered using local payment providers from the start, rather than trying to work around their limitations. But I learned a valuable lesson: when it comes to building a platform that serves a specific region or community, you can’t rely on generic solutions. You need to build a solution that is tailored to the specific needs of your users, and that requires a deep understanding of the underlying technology and infrastructure.

Digital Payments in Africa: A System Designer’s Lament

The Problem We Were Actually Solving

Our client pool was largely made up of African creatives trying to monetize their work online. But when we tried to integrate the usual payment processors, we hit a brick wall: no matter what combination of payment flows and API keys we tried, we just couldn’t get it to work. The errors were always the same: “payment declined due to regional restrictions” or simply “your account has been flagged for suspicious activity.” It was as if we were trying to solve a problem that didn’t exist, but clearly did.

What We Tried First (And Why It Failed)

We started by trying to massage the existing payment flows to work around the region restrictions. We tweaked the API requests, adjusted the currencies, and even tried using different payment gateways (e.g., Paystack for Nigeria, M-Pesa for Kenya). But no matter what we did, we just couldn’t get it to work. It wasn’t until we started digging deeper into the problem that we realized the issue wasn’t with our code or the payment processors – it was with the underlying architecture of the system itself.

The Architecture Decision

We decided to take a step back and rethink the entire payment processing system from the ground up. We opted to use Mollie, a Dutch-based payment processor that specialized in multi-currency transactions and regional-specific payment flows. The decision was based largely on their experience with African markets and their ability to bypass the usual regional restrictions. We also implemented a custom payment flow using the Interchangeable Payment Interface (IPI), a set of APIs that allowed us to swap out different payment processors as needed.

What The Numbers Said After

After implementing the new payment processing system, we saw a significant decrease in payment errors (from 25% to 5%) and an increase in successful transactions (from 40% to 75%). We also saw a corresponding decrease in support requests related to payment issues (from 30% to 10%). The numbers spoke for themselves: our new system was not only more reliable but also more user-friendly.

What I Would Do Differently

Looking back, I wish we’d taken a more aggressive approach to identifying and mitigating regional restrictions from the outset. We could have saved a significant amount of time and resources by focusing on finding a payment processor that specialized in African markets from the get-go. That being said, the experience taught us a valuable lesson about the importance of system architecture and the need to think beyond the constraints of existing systems. As engineers, we need to be willing to challenge assumptions and explore new solutions – even if it means starting from scratch.

# How to Validate UK VAT Numbers, NINO, Company Numbers and UTR in Any Language (2026)

How to Validate UK VAT Numbers, NINO, Company Numbers and UTR in Any Language (2026)

If you’re building software for the UK market, you’ll eventually need to validate fiscal and identification documents. This guide covers the algorithms, code examples, and the fastest way to add validation to any project.

What are these documents?

  • VAT number — Tax ID for UK businesses registered for VAT. Format: GB + 9 digits (e.g. GB123456789)
  • NINO — National Insurance Number, used for tax and social security. Format: 2 letters + 6 digits + letter (e.g. AB123456C)
  • Company Number — Companies House registration number. Format: 8 digits or prefix + 6 digits (e.g. SC123456 for Scottish companies)
  • UTR — Unique Taxpayer Reference, used for Self Assessment. Format: 10 digits (e.g. 1234567890)

Option 1: Implement it yourself

VAT number validation (Python)

UK VAT numbers use HMRC’s mod-97 checksum on the first 7 digits:

def validate_uk_vat(vat: str) -> bool:
    vat = vat.strip().upper().replace(" ", "")
    if vat.startswith("GB"):
        vat = vat[2:]
    if len(vat) not in (9, 12) or not vat.isdigit():
        return False

    digits = [int(d) for d in vat[:7]]
    weights = [8, 7, 6, 5, 4, 3, 2]
    total = sum(d * w for d, w in zip(digits, weights))
    check = int(vat[7:9])

    return check == (97 - total % 97) or check == (97 - (total + 55) % 97)

NINO validation (JavaScript)

function validateNINO(nino) {
  nino = nino.replace(/s/g, '').toUpperCase();
  const invalidFirst = /[DFIQUV]/;
  const invalidSecond = /[DFIOQUV]/;
  const invalidPrefixes = ['BG','GB','NK','KN','NT','TN','ZZ'];

  if (!/^[A-Z]{2}d{6}[ABCD]$/.test(nino)) return false;
  if (invalidFirst.test(nino[0])) return false;
  if (invalidSecond.test(nino[1])) return false;
  if (invalidPrefixes.includes(nino.slice(0,2))) return false;
  return true;
}

Company number validation (Python)

PREFIXES = {'SC', 'NI', 'OC', 'SO', 'FC', 'BR', 'CE', 'CS'}

def validate_company_number(number: str) -> bool:
    number = number.strip().upper()
    if number[:2] in PREFIXES:
        return len(number) == 8 and number[2:].isdigit()
    return number.isdigit() and len(number) <= 8

Option 2: Use an API (3 lines of code)

For VAT the algorithm is manageable, but NINO has 15+ edge cases and Company Numbers have 15+ prefix types. If you don’t want to maintain all of this, the UK Document Validator API handles everything:

Python

import requests

r = requests.get(
    'https://uk-document-validator1.p.rapidapi.com/validate/vat',
    params={'value': 'GB123456789'},
    headers={
        'X-RapidAPI-Key': 'YOUR_API_KEY',
        'X-RapidAPI-Host': 'uk-document-validator1.p.rapidapi.com'
    }
)
print(r.json())
# {'input': 'GB123456789', 'valid': True, 'type': 'VAT', 'country': 'GB', 'formatted': 'GB123 456 789'}

JavaScript

const r = await fetch(
  'https://uk-document-validator1.p.rapidapi.com/validate/nino?value=AB123456C',
  {
    headers: {
      'X-RapidAPI-Key': 'YOUR_API_KEY',
      'X-RapidAPI-Host': 'uk-document-validator1.p.rapidapi.com'
    }
  }
);
const data = await r.json();
// { valid: true, type: 'NINO', formatted: 'AB 12 34 56 C' }

PHP

$curl = curl_init();
curl_setopt_array($curl, [
    CURLOPT_URL => 'https://uk-document-validator1.p.rapidapi.com/validate/company?value=SC123456',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-RapidAPI-Key: YOUR_API_KEY',
        'X-RapidAPI-Host: uk-document-validator1.p.rapidapi.com'
    ],
]);
$r = json_decode(curl_exec($curl));
// $r->company_type = "Scottish company"

API response examples

VAT:

{ "input": "GB123456789", "valid": true, "type": "VAT", "country": "GB", "formatted": "GB123 456 789" }

NINO:

{ "input": "AB123456C", "valid": true, "type": "NINO", "formatted": "AB 12 34 56 C" }

Company Number:

{ "input": "SC123456", "valid": true, "type": "COMPANY_NUMBER", "company_type": "Scottish company", "formatted": "SC123456" }

Auto-detect (any document type):

{ "input": "1234567890", "valid": true, "type": "UTR", "detected_as": "UTR", "formatted": "12345 67890" }

When to use the API vs DIY

DIY API
VAT validation ~15 lines 3 lines
NINO edge cases 15+ rules to implement Included
Company prefix types 15+ prefixes Included
UTR checksum Complex Included
Auto-detect document type Extra logic Included
Maintenance Your problem Automatic

Free tier: 500 requests/month. Subscribe here.

Full docs and interactive playground: uk-validation-api.vercel.app/docs

Chat with your database in plain English — locally, for free

“What were our top 10 customers last quarter by revenue, as a bar chart?”

DB-GPT translates that to SQL, runs it against your database, and renders the chart. No SQL knowledge required. Fully local. MIT licensed. 17K GitHub stars — almost no English content.

What is DB-GPT?

DB-GPT is an open-source framework that puts a natural language interface on top of your databases. You connect PostgreSQL, MySQL, SQLite, or others — then ask questions in plain English. It generates the SQL, executes it, and can visualize results automatically.

Think Metabase meets AI, but fully self-hosted and free.

Supported Databases

PostgreSQL · MySQL · MariaDB · SQLite · ClickHouse · DuckDB · Spark SQL

Setup

Clone the repo, copy .env.example to .env, add your database connection string, then docker compose up -d. Open localhost:5670 → admin / admin → Settings → Database → Add → paste connection string.

Full docker-compose: chinese-ai-tools-english-guide/tools/db-gpt

Example Queries

Once your database is connected:

  • “Show total sales by month for the last 12 months as a bar chart”
  • “Which products have inventory below 10 units?”
  • “Top 5 customers by order value with their email addresses”
  • “Average order fulfillment time grouped by warehouse”

DB-GPT generates SQL for each, runs it, returns results. Charts render automatically in the UI.

Connecting Ollama

Settings → LLM Provider → Ollama → Base URL: http://ollama:11434/v1 → Model: llama3

For best SQL accuracy use sqlcoder — fine-tuned specifically for SQL generation. Pull it with docker exec -it ollama ollama pull sqlcoder.

DB-GPT vs Vanna.ai

Both let you query databases with natural language:

DB-GPT Vanna.ai
License MIT MIT
Built-in UI ✅ Full app Minimal
Charts ✅ Built-in ❌ External
Visual pipeline ✅ AWEL ❌
Self-hosted ✅ ✅

DB-GPT if you want a complete self-contained app. Vanna.ai if you’re embedding the capability programmatically into your own product.

n8n Automation

A ready-to-import workflow JSON is in the repo (integration/n8n-workflows/db-gpt-query.json). POST {question, db_name} → returns {answer, sql, data}.

Full Guide

→ chinese-ai-tools-english-guide

Previous articles in this series:

Your data never leaves your machine. No API keys, no cloud, no SQL knowledge needed.

Benchmarking AWS Nova on Log Data: How It Compares to ChatGPT-3.5

Authored by Benoit Gaudin

This post explores the use of large language models (LLMs) for analyzing log data. To do so, we reproduced part of the An Assessment of ChatGPT on Log Data benchmark, originally conducted in 2023 by Intel researchers Priyanka Mudgal and Rita Wouhaybi.

While that initial benchmark used ChatGPT-3, our study evaluates the AWS Nova Micro model. Our goal: assess whether more recent, smaller, and cheaper models can match — or exceed — the performance of ChatGPT-3 from a few years ago. The economics are particularly interesting: Nova Micro’s cost per input token is 14 times lower than GPT-3.5-turbo was two years ago.

Benchmark Setup

The original benchmark evaluated GPT-3.5-turbo across ten research questions grouped into four categories:

  1. Log Parsing & Analytics — Can the model parse logs and identify errors, root causes, security events, and anomalies? Can it identify frequently used APIs?
  2. Prediction — Can it predict future log events based on past logs?
  3. Summarization — Can it summarize single and multiple log messages?
  4. General Capabilities — Can it handle bulk log data, and what message lengths can it process?

Experiments used datasets from the Loghub collection — 2,000 labeled log messages from various systems (Windows, Linux, mobile, distributed, etc.).

Our experiment reused the same methodology and the same 19 Loghub datasets, with these differences:

  • We evaluated AWS Nova Micro rather than GPT-3.5-turbo
  • We focused on the first three categories (7 questions) — the fourth category covers context window size, which is no longer a meaningful differentiator (GPT-3.5-turbo: 16,385 tokens; Nova Micro: 128,000 tokens)
  • Where the original benchmark tested multiple input sizes (e.g. 5, 10, 50 log entries), we used only the maximum (50), to give the model the most context
  • Results were manually evaluated by a human, using the same prompts as the original
Category Question Prompt Description
Log Parsing Q1 Extract the log template and variables from this log message. How does the model perform on log parsing?
Log Analytics Q2 Summarize the errors and warnings and identify the root cause. Can it extract errors and root causes from raw logs?
Log Analytics Q3 Show the APIs called most with count. Can it perform advanced analytics tasks?
Log Analytics Q4 Are there any malicious users, URLs, IPs, and connection status? Can it extract security information?
Log Analytics Q5 Detect the anomalies from the following log messages. Can it detect anomalies?
Log Analytics Q6 Predict the next 10 log events based on these log messages. Can it predict future events?
Log Summarization Q7 Summarize the log message. Can it summarize a single log message?

Results: AWS Nova Micro’s Performance

Prompt Correct Answers Remarks
Extract log template and variables 17/19 (89%) Failed on HDFS logs; IDs not always categorized accurately
Summarize errors and identify root cause 10/19 (53%) Erroneously reports warnings in Hadoop logs; confuses timestamps and error codes in HPC; over-reports issues in HealthApp and Mac logs
Show most-called APIs with count 4/19 (21%) Counting is very challenging; many datasets lack API-related entries; model over-reports results that don’t make sense
Detect malicious users, URLs, IPs 18/19 (95%) High accuracy, but hard to conclude on the general case as no obvious security issues were present in the sampled logs
Detect anomalies 9/19 (47%) Reports anomalies based on irrelevant criteria (e.g. entries that “occur towards the end of the sample” or are “repetitive”)
Predict next 10 log events 0/19 (0%) Even for extremely repetitive logs, IDs and timestamps are not predicted correctly
Summarize a single log message 16/19 (84%) Good results overall; challenging for unfamiliar log formats without named fields

In summary, our evaluation confirms the findings of the original benchmark: similar to ChatGPT-3, Nova Micro performs well at parsing and summarizing log data. Other types of analysis — counting, anomaly detection, prediction — remain challenging for LLMs.

The malicious content detection result (95%) looks strong, but needs a caveat: the sampled datasets didn’t contain clearly malicious entries. The model didn’t produce false positives here, which is valuable in itself — especially compared to anomaly detection, where false positives were common.

This benchmark demonstrates that it is now possible to achieve parsing and summarising of log data in a much more cost-effective way.

Reflection on Datasets

The Loghub collection is invaluable for reproducible benchmarking — without it, meaningful cross-benchmark comparisons would be impossible. That said, the datasets have some limitations worth noting.

At Bronto, we work frequently with log types common in real-world production environments: CDN logs, web access logs, AWS CloudTrail audit logs, application logs. LLMs tend to have a strong understanding of these formats because they’re widely documented and structured.

Structured logs change the picture significantly. When we ran Q2 and Q3 prompts against synthetic structured CDN log data (based on real examples), the model performed substantially better:

  • For Q2 (error identification), the model perfectly identified HTTP errors by associating status codes ≥ 400 with errors — even though the field name never used the word “error”. It correctly categorized 400 (Client-Side), 404 (Not Found), 500 (Internal Server Error), and 503 (Service Unavailable).
  • For Q3 (most-called APIs), the model correctly identified the reqPath field as representing API endpoints and extracted the top results accurately.

Counting remains a consistent weakness across all dataset types. When Q3 requires providing a count of the most common API calls, the model’s counts are frequently inaccurate regardless of dataset.

One additional observation: several Loghub datasets (HPC, HealthApp, BGL, Proxifier) appear to be uncommon enough that Nova Micro doesn’t have a solid prior understanding of them. When asked to generate sample logs for these systems, the output doesn’t resemble the actual Loghub data — suggesting the model is less reliable when operating outside familiar territory.

Conclusion

This benchmark reproduces the 2023 ChatGPT log analysis study using AWS Nova Micro. The results are strikingly similar — with one major difference: the cost per token is 14x lower.

Given that log data is notoriously voluminous, this cost difference matters enormously for any production use of LLMs in log analysis pipelines.

The Loghub datasets are also not fully representative of what most production logging systems generate. Real-world logs — web access, CDN, application, audit — tend to be more structured and more familiar to LLMs, which leads to better performance than the benchmark scores suggest.

We believe LLMs have genuine potential to improve production logging systems, particularly for analyzing the common, structured log formats that make up the majority of real-world observability data.

Explore Bronto’s AI Features

Strands Agents + AgentCore Runtime – a perfect match

CodeBuild Project

This is the third in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent – a personalised AWS assistant deployed on Amazon Bedrock AgentCore Runtime.

The initial implementation of the AWS Briefing Agent called the AWS News Feed RSS feed on every invocation. After setting up an Amazon Bedrock Knowledge Base, the next step was to refactor the code to take advantage of an agentic framework. The decision was made to adopt Strands Agents SDK as an open source SDK that helps you build and run AI agents in just a few lines of code. In our case, switching to the Knowledge Base and adopting Strands Agents SDK helped us to reduce the number of lines of code in our implementation logic by 75%.

Using Strands Agents SDK

The core of the Strands Agents code is straightforward and shown in the code snippet below:

from strands import Agent
from strands.models import BedrockModel
from strands.agent.conversation_manager import SlidingWindowConversationManager
from strands_tools import retrieve
from agent.tools.slack_formatter.tool import format_slack_message

model = BedrockModel(
    guardrail_id=GUARDRAIL_ID,
    guardrail_version=GUARDRAIL_VERSION,
    guardrail_trace="enabled",
)

agent = Agent(
    system_prompt=_load_system_prompt(),
    model=model,
    tools=[retrieve, format_slack_message] + gateway_tools,
    session_manager=session_manager,
    conversation_manager=SlidingWindowConversationManager(
        window_size=20,
        should_truncate_results=True,
        per_turn=True,
    ),
    callback_handler=None,
)

result = agent(message)

We start by importing a number of classes and functions from two packages (strands-agents and strands-agents-tools) and one local module. Agent is the core class for the agent itself, BedrockModel is the model provider, SlidingWindowConversationManager controls how conversation history is trimmed, and retrieve is a pre-built tool that is used to query a Bedrock Knowledge Base. The format_slack_message is a local custom tool within this project – a Python function decorated with the @tool annotation.

We instantiate the BedrockModel() without specifying a model_id. At this point, Strands uses its default model, which is current Claude Sonnet on Bedrock. We include details of a Bedrock Guardrail when we instantiate the model, purely to demonstrate the use of guardrails which we cover this later in the blog post.

Finally, we create the agent by wiring together its core components.

Deploy to Amazon Bedrock AgentCore Runtime

The AgentCore Runtime Python SDK provides a lightweight wrapper that helps to deploy your agent function as HTTP services

# Import the runtime
from bedrock_agentcore.runtime import BedrockAgentCoreApp

# Initialise the app
app = BedrockAgentCoreApp()

# Decorate the function
@app.entrypoint
def invoke(payload: Dict[str, Any], context: Any = None) -> Dict[str, Any]:
    """Entry point for AgentCore Runtime."""
    message = payload.get("prompt", payload.get("message", ""))
    ...
    return response

BedrockAgentCoreApp wraps your function in an HTTP server that listens om port 8080 with two endpoints:

  • /invocations – a POST endpoint for agent interactions. This gets invoked when customers call the InvokeAgentRuntime action with the payload in JSON format
  • /ping – a GET endpoint for health checks to verify your agent is operational and ready to handle requests

The @app.entrypoint decorator registers your invoke function as the handler for incoming requests. When AgentCore Runtime receives a request, it deserialises the JSON body into payload, provides a context object (with session_id, request_headers, etc.), calls your function, and serialises the returned dict back as the HTTP response.

Using the Container Build

When using the @aws/agentcore CLI and running agentcore deploy, the CLI needs to turn the Python source code into a runnable container image on AgentCore Runtime. This is controlled by the build field in the agentcore.json file. The default setting is CodeZip, in which the CLI zips up the Python source code, uploads it, and AgentCore resolves dependencies using uv --no-build. This is fast but has a hard constraint, as every dependency must have a pre-built wheel. In our code, we have a package that only ships source distributions, which required us to switch to the Container build setting. This also makes our build more production-ready.

When you run agentcore deploy with the Container build type, the CLI synthesis a CloudFormation stack that includes a CodeBuild project, an ECR repository, the AgentCore Runtime resource, and IAM roles. The CLI packages the codeLocation directory (agent/) and uploads it to S3 as the CodeBuild source artefact. CodeBuild pulls the provided Dockerfile and builds the container image. You can see all the steps in the CodeBuild project below:

CodeBuild Project

After the image builds successfully, CodeBuild tags it and pushes it to the ECR repository as shown below:

Amazon ECR Repository

The stack updates the Runtime resource to point at the new ECR image URI. AgentCore pulls the image from ECR the next time it starts a container for an invocation.

Built-In Conversation Managers

In the Strands Agents SDK, the user messages and agent responses are all added to the context. As the conversation grows within a session, this starting having a material impact on response times. We modified the default SlidingWindowConversationManager manager:

  • reducing the windowSize from the default of 40 to 20. This sets the maximum number of messages to keep
  • setting the per_turn parameter to false. This runs the sliding window before every model call within the same invocation, rather than waiting until after the agent loop completes.

This reduced the average response time from around 80 seconds down to 15 seconds.

Adding Bedrock Guardrails

Amazon Bedrock Guardrails are designed to help you safely build and deploy responsible generative AI applications with confidence. We decided to include a guardrail in the architecture, to understand where it fits in and what it can provide.

The guardrail itself was defined in CDK with content filters (sexual, violence, hate, insults, misconduct and prompt attack), a topic policy (deny off-topic sports questions), and a managed profanity word list:

# ----------------------------------------------------------------
# Bedrock Guardrail — content safety for the agent
# ----------------------------------------------------------------
guardrail = bedrock.CfnGuardrail(
    self,
    "BriefingAgentGuardrail",
    name="briefing-agent-guardrail",
    description="Content safety guardrail for the AWS Briefing Agent",
    blocked_input_messaging="I'm sorry, I can't process that request. Please rephrase your question about AWS announcements.",
    blocked_outputs_messaging="I'm sorry, I can't provide that response. Let me try a different approach.",
    content_policy_config=bedrock.CfnGuardrail.ContentPolicyConfigProperty(
        filters_config=[
            bedrock.CfnGuardrail.ContentFilterConfigProperty(
                type="SEXUAL",
                input_strength="HIGH",
                output_strength="HIGH",
            ),
            bedrock.CfnGuardrail.ContentFilterConfigProperty(
                type="VIOLENCE",
                input_strength="HIGH",
                output_strength="HIGH",
            ),
            # HATE, INSULTS, MISCONDUCT, PROMPT_ATTACK
        ],
    ),
    topic_policy_config=bedrock.CfnGuardrail.TopicPolicyConfigProperty(
        topics_config=[
            bedrock.CfnGuardrail.TopicConfigProperty(
                name="Sports",
                definition="Questions about sports scores, match results, player transfers, league standings, fixtures, or any sporting events.",
                type="DENY",
            ),
        ],
    ),
    word_policy_config=bedrock.CfnGuardrail.WordPolicyConfigProperty(
        managed_word_lists_config=[
            bedrock.CfnGuardrail.ManagedWordsConfigProperty(
                type="PROFANITY",
            ),
        ],
    ),
)

When the agent is invoked, the request first reaches the AgentCore Runtime and runs the handler code first. The guardrail itself is only applied when the handler makes the Bedrock inference call. Bedrock evaluates the input before running the model inference, and then inspects the output before returning it. We did encounter some interesting behaviour when implementing the guardrail.

IAM Permission Gap

The first invocation after adding the guardrail failed with:

AccessDeniedException: User is not authorized to perform: bedrock:ApplyGuardrail
on resource: arn:aws:bedrock:eu-west-1.xxx

The AgentCore execution role (auto-created by the @aws/agentcore-cdk construct) includes bedrock:InvokeModel and bedrock:InvokeModelWithResponseStream, but not bedrock:ApplyGuardrail. The construct doesn’t know about guardrails — they’re a Bedrock feature, not an AgentCore feature. We ended up having to use the aws iam put-role-policy CLI command to add the missing permission

Topic policies can false-positive on legitimate queries

The initial topic policy denied “questions not related to AWS services, cloud computing, or technology”. The intention was that it would be easy to demonstrate, and would ensure that the user input was relevant. However, when the user asked questions such as “what are the top announcements today”, the classifier ended up deciding this was a blocked topic. In the end, to demonstrate how topic policies work, we changed it to explicitly deny sporting questions.

Guardrail versions can be deleted by CDK updates

When we updated the topic policy, we changed the version description for the guardrail. The CDK stack updated the guardrail version resource, so that CloudFormation deleted version 1 and created version 2. Unfortunately, the version number is also defined in the agentcore.json file. This meant that the AgentCore Runtime container still had version 1 baked into its environment, which meant calls now failed with the following exception:

ValidationException: The guardrail identifier or version provided in the request does not exist.

In the end it was a case of having to update the version number in agentcore.json, redeploy the agent, and start a new session.

Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering

This is the second in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent – a personalised AWS assistant deployed on Amazon Bedrock AgentCore Runtime.

When I started building the AWS Briefing Agent, the first version queried the AWS What's New RSS feed on every invocation. This worked in terms of showing the agent could return tailored information back to the client. However, it was costly and wasteful, with the same data fetched repeatedly, which added latency to every invocation. The RSS feed also only covers recent information, and it was likely we would want to start searching for releases that had been launched in the past 6 months or more. The next step therefore, was to separate the retrieval by the agent from the ingestion.

Amazon Bedrock Knowledge Base

One of the key design goals was to allow the agent to match a natural language query “what’s new in Bedrock this week?” against a large corpus of documents to return the most semantically similar results. This is where Amazon Bedrock Knowledge Base comes into its own. It allows the agent to use RAG (Retrieval-Augmented Generation). By querying the Knowledge Base, we can retrieve relevant documents at query time, and then inject them into the prompt as context. The LLM then generates a response from this retrieved information which we know to be factual.

The python CDK code that creates the Knowledge Base is shown below:

knowledge_base = bedrock.CfnKnowledgeBase(
    self,
    "AnnouncementKnowledgeBase",
    name="aws-briefing-agent-announcements",
    ...
    knowledge_base_configuration=bedrock.CfnKnowledgeBase.KnowledgeBaseConfigurationProperty(
        type="VECTOR",
        vector_knowledge_base_configuration=bedrock.CfnKnowledgeBase.VectorKnowledgeBaseConfigurationProperty(
            embedding_model_arn=f"arn:aws:bedrock:{self.region}::foundation-model/amazon.titan-embed-text-v2:0",
        ),
    ),
    storage_configuration=bedrock.CfnKnowledgeBase.StorageConfigurationProperty(
        type="S3_VECTORS",
        s3_vectors_configuration=bedrock.CfnKnowledgeBase.S3VectorsConfigurationProperty(
            index_name="announcements",
            vector_bucket_arn=f"arn:aws:s3vectors:{self.region}:{self.account}:bucket/briefing-agent-vectors",
        ),
    ),
)

This declares the embeddings model to be used as amazon.titan-embed-text-v2:0 and the vector store as being of type S3_VECTORS. There is no code required to handle aspects such as embeddings. Instead, Bedrock manages all of this for us.

Amazon S3 Vectors

Amazon Bedrock Knowledge Bases support several vector stores. A vector store is the retrieval engine that makes RAG work. It stores documents as numerical embeddings (vectors) that are generated by an embeddings model. At query time, the user’s question is embedded, and the vector store finds documents whose embeddings are closest in meaning.

The prototype uses Amazon S3 Vectors as the underlying vector store. S3 Vectors provides cost-effective, elastic, and durable vector storage at up to 90% lower costs for uploading, storing, and querying vectors than alternatives such as OpenSearch Serverless. There is no infrastructure to manage, and it still provides a sub-second query latency which is acceptable for this use case.

Scheduling the Ingestion

The ingestion pipeline is run every 6 hours using Amazon EventBridge Scheduler. This service provides capabilities such as built-in retry policies, time zone support, and dead-letter queues. The schedule triggers an AWS Lambda function that carries out the required processing. This includes:

  1. Lists existing document hashes in S3
  2. Fetches the AWS What’s New RSS feed (~100 announcements)
  3. Fetches 13 AWS blog RSS feeds (aws, machine-learning, compute, security,
    database, containers, devops, networking, storage, infrastructure-and-automation,
    developer, big-data, iot)
  4. Fetches the AWS Security Bulletins RSS feed
  5. For each new blog post, fetches the canonical URL and extracts the full article body
    using a stdlib HTML parser
  6. Parses publication dates into YYYYMMDD integers
  7. Writes .txt and .metadata.json files per new item to S3
  8. Triggers a Bedrock KB ingestion job

Deduplication and Incremental Writes

When the ingestion pipeline runs, most of the content in the various RSS feeds is not new. It was important to find a way to prevent re-fetching and re-writing hundreds of announcements every 6 hours.

To support this, we created an MD5 hash of the blog posts URL, truncated to 12 hex characters. This hash is used as the S3 filename. The sample code snippet is shown below:

def write_to_s3(items, existing_keys=None):
    existing = existing_keys or set()
    for item in items:
        url_hash = hashlib.md5(item["link"].encode()).hexdigest()[:12]
        if url_hash in existing:
            continue # Already in S3, skip
        # ... write doc + metadata files

At startup, get_existing_keys() lists all the .txt files in S3 and extracts the hash from each filename into a set. When processing the blog posts, the Lambda functions computes the URL hash and checks to see if it is already in the set. If it already exists, then it has been ingested in a previous run, and there is no need to re-fetch the page. If the hash does not exist, then the function fetches the page, extracts the content, and writes to S3. The hash gives a stable, deterministic filename derived from the URL. The same URL always produces the same hash.

Chunking Strategy

The chunking strategy is set on the Data Source resource in the CDK stack as shown below:

data_source = bedrock.CfnDataSource(
    self,
    "AnnouncementDataSource",
    name="aws-announcements-s3",
    knowledge_base_id=knowledge_base.attr_knowledge_base_id,
    data_source_configuration=bedrock.CfnDataSource.DataSourceConfigurationProperty(
        type="S3",
        s3_configuration=bedrock.CfnDataSource.S3DataSourceConfigurationProperty(
            bucket_arn=data_bucket.bucket_arn,
        ),
    ),
    vector_ingestion_configuration=bedrock.CfnDataSource.VectorIngestionConfigurationProperty(
        chunking_configuration=bedrock.CfnDataSource.ChunkingConfigurationProperty(
            chunking_strategy="SEMANTIC",
            semantic_chunking_configuration=bedrock.CfnDataSource.SemanticChunkingConfigurationProperty(
                breakpoint_percentile_threshold=92,
                buffer_size=1,
                max_tokens=600,
            ),
        ),
    ),
)

We utilise a SEMANTIC chunking strategy. This uses the embedding model itself to decide where to split. The following three parameters control this behaviour:

  • breakpoint_percentile_threshold=92 – controls the percentile threshold that will result in a split. A higher threshold requires sentences to be more distinguishable to split the document into different chunks.
  • max_tokens=600 – the maximum number of tokens that should be included in a single chunk, while honoring sentence boundaries.
  • buffer_size=1 – for a given sentence, the buffer size defines the number of surrounding sentences to be added for embeddings creation. A larger buffer size might capture more context but can also introduce noise, while a smaller buffer size might miss important context but ensures more precise chunking.

Filtering by Date

One of the goals in writing the agent was that a user could ask to constrain information by how recent it is e.g. “what is new in the past 7 days?”.

To help achieve this, at ingestion time for each document, we create an associated metadata.json sidecar file that attaches structured, filterable attributes to a document so the agent can narrow search results without relying only on semantic similarity. An example companion file is shown below:

{
  "metadataAttributes": {
    "published_date": 20260415,
    "service": "amazon-bedrock",
    "category": "artificial-intelligence",
    "source_type": "announcement"
  }
}

During the Knowledge Base sync, Bedrock reads this sidecar and attaches those attributes to every vector chunk generated from that document. At query time, the agent can combine semantic search with metadata filters:

  • “What’s new in Bedrock this week?” → vector similarity for “Bedrock” + greaterThanOrEquals filter on published_date
  • “Show me security bulletins” → vector similarity + equals filter on source_type: “security-bulletin”
  • “Lambda announcements from the last month” → vector similarity + filters on both service and published_date

Without the metadata file, the agent would get the most semantically similar results regardless of date or service — so a question about “this week” might return announcements from 3 months ago that happen to be textually similar. The metadata filters let the agent constrain results to the correct time window or service before ranking by relevance.

The naming convention (.metadata.json) is a Bedrock KB convention — it automatically associates the sidecar with its parent document during ingestion. No code links them; the filename pattern is enough.

Bedrock Knowledge Base metadata supports four types: STRING, NUMBER, BOOLEAN and STRING_LIST. There is no native data type. The comparison operators (greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals) only work with NUMBER. Our original implementation stored published_date as a string (“2026-05-14”). When the agent tried to filter, we got back the following exception:

ValidationException: The filter value type provided isn't supported
for the given operation: GREATER_THAN_OR_EQUALS

The fix was to store dates as YYYYMMDD numbers (so using “20260514” instead of “2026-05-14”). We also inject today’s date into the system prompt at runtime so the LLM can easily calculate relative dates.

Note that Amazon S3 Vectors has a strict 2 KB limit on filterable metadata per vector. We found the Bedrock Knowledge Base internal metadata keys (AMAZON_BEDROCK_TEXT and AMAZON_BEDROCK_METADATA) were set as filterable by default, which caused frequent ValidationException errors. The fix was mark both of these keys as non-filterable when creating the vector index:

vector_index = s3vectors.CfnIndex(
    self, "AnnouncementVectorIndex",
    index_name="announcements",
    vector_bucket_name=vector_bucket.vector_bucket_name,
    dimension=1024,  # Titan Embed Text v2
    distance_metric="cosine",
    data_type="float32",
    metadata_configuration=s3vectors.CfnIndex.MetadataConfigurationProperty(
        non_filterable_metadata_keys=[
            "AMAZON_BEDROCK_TEXT",
            "AMAZON_BEDROCK_METADATA",
        ],
    ),
)

This meant the only filterable metadata is contained in the .metadata.json fields, which are the only fields we filter on.

Building a Full-Stack AI Agent on Amazon Bedrock AgentCore

Briefing Agent Architecture

This is the first in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent – a personalised AWS assistant deployed on Amazon Bedrock AgentCore Runtime.

Why build an agent?

The last few years have seen a rapid shift from Generative to Agentic AI. It began with assistants that would take in a prompt and return a response – impressive at the time, but still largely reactive and reliant on human input. We then saw the emergence of early AI agents that could break down tasks into smaller steps and execute them independently. Over the past year, this has evolved into fully autonomous multi-agent systems capable of completing complex tasks with minimal or even no human supervision.

The shift is accelerating quickly. Gartner predicts that by 2028, more than a third of all enterprise software apps will include Agentic AI, and at least 15% of day-to-day work decisions will be made autonomously by AI agents. For organisations, the question is no longer whether agents will become part of enterprise systems, but how to build them securely, reliably and operate them at scale.

The goal of this blog series is to build out an agentic application using various features of Amazon Bedrock AgentCore, to help better understand the AgentCore capabilities and showcase how they can be used together.

Why AgentCore Runtime?

Amazon Bedrock AgentCore is an AWS service that has been designed specifically for the task of hosting agents. A common saying I keep on hearing is that Bedrock AgentCore is to agentic applications what AWS Lambda is to event driven applications.

At the heart is AgentCore Runtime, which provides the secure runtime for executing the agent code. AgentCore Runtime provides session-based isolation, where every session is assigned a dedicated Firecracker microVM with isolated CPU, memory and filesystem resources (the same lightweight virtualisation technology that underpins AWS Lambda and AWS Fargate). When the session finishes, the LLM’s state information is copied to long-term memory and the entire microVM is destroyed. There is no shared state between sessions, which prevents any cross-session data leakage.

AgentCore Runtime is framework-agnostic and supports all popular frameworks such as Strands Agents, LangGraph and CrewAI. It also works with any LLM, such as models offered by Amazon Bedrock, Anthropic Claude, Google Gemini and OpenAI or even hosted on-premises. It supports long sessions up to 8 hours, which means it can handle complex multi-step tasks or time-consuming background processes. Unlike traditional compute services that charge for pre-allocated resources, AgentCore Runtime uses consumption-based pricing where you only pay for active CPU and memory usage. With this, I/O wait and idle time is free, and you’re only charged for actual resource consumption calculated at per-second increments. The runtime automatically scales from zero to thousands of concurrent sessions on demand, with no capacity planning needed, and includes reliability features like checkpointing to recover gracefully from interruptions.

AWS Briefing Agent Architecture

A high-level architecture overview of the AWS Briefing Agent is shown below:

Briefing Agent Architecture

  • AWS Briefing Agent Client is a next.js static site hosted on AWS Amplify Hosting. It integrates directly with Amazon Cognito using the amazon-cognito-identity-js SDK, implementing a full sign-in, sign-up and email verification flow.

  • AWS Briefing Agent itself is a Python application built with the Strands Agents SDK and deployed to AgentCore Runtime as a Docker container. The @aws/agentcore CLI handles the full deployment lifecycle. When you run agentcore deploy, the CLI triggers AWS CodeBuild to build the Docker image (ARM64), pushes it to Amazon ECR, and deploys it to AgentCore Runtime.

  • AgentCore Memory provides persistent user knowledge across sessions using two built-in memory strategies. The SEMANTIC memory strategy extracts factual information and knowledge from conversations that have taken place e.g. that a user works with Lambda and EKS. The USER_PREFERENCE memory strategy identifies and extracts user preferences from conversations e.g. that the user prefers technical deep dives. The agent retrieves relevant memory records at the start of each invocation and injects them as context, enabling personalised briefings from the first message of a new session.

  • AgentCore Observability is used to instrument all Bedrock API calls, tool invocations and memory operations. This is carried out entirely by setting enableOtel: true in the runtime config and using the opentelemetry-instrument wrapper command. Spans show up in CloudWatch Transaction Search and the CloudWatch GenAI Observability dashboard is populated with the sessions and traces, and provides the abiliyty to drill into individual invocations.

  • AgentCore Evaluations is configured to run online quality assessments against agent responses using built-in evaluators for Helpfulness, Goal Success Rate, and Correctness. These are shown in the front-end to give an indication on how well the agent is performing for each user.

  • Bedrock Knowledge Base is created and backed by Amazon S3 Vectors that stores all announcements, blog posts and security bulletins. An ingestion Lambda runs every 6 hours that writes each item as a .txt file alongside a metadata.json file to the S3 bucket, before triggering a Knowledge Base sync. The agent queries the KB via the Strands retrieve tool with metadata filters for date ranges and service names, enabling questions like “what’s new in Bedrock this week?”

  • AgentCore Gateway exposes a managed MCP (Model Context Protocol) endpoint that the agent connects to at runtime for tool discovery. The Slack integration is defined as an OpenAPI spec pointing at the Slack chat.postMessage API, and is registered as a Gateway target. The agent discovers available tools dynamically via the MCP protocol. The Gateway handles authentication and credential injection for this integration with Slack, attaching the stored bot token as a Bearer header on outbound Slack API calls.

  • AgentCore Identity stores the Slack bot token as an API key credential in its token vault (encrypted at rest via Secrets Manager). When the agent calls the tool to send a briefing to Slack, AgentCore Identity retrieves the bot token and injects it into the outbound request automatically. The agent code never sees or handles the token directly.

  • AgentCore Registry is a governed catalog for agents, MCP servers, tools, skills, and custom resources. Teams can publish resources, control access through approval workflows, and enable both humans and AI agents to discover tools using semantic and keyword search. Once the Slack integration was working, the briefing agent and the Slack tool where registered in the AgentCore Registry. This makes the tool discoverable by other agents in the organisation.

AWS Briefing Agent in Action

We create a new user and login to the home screen for the AWS Briefing Agent front end. The first time we use the agent, we are asked to provide information about our interests and the type of briefing style we are interested in. These get added to memory, so that the agent can personalise its responses:

AWS Briefing Agent Home Page

We can provide the details of the services we are most interested to the agent. At this point, the agent will pull back the top announcements that it has retrieved from the Knowledge Base, and display them in a briefing summary.

AWS Briefing Agent Summary

We have also integrated with Slack through Gateway. This means we can ask the Briefing Agent to post the details to our Slack channel:

AWS Briefing Agent Send to Slack

This means that when we go to our Slack channel, we can see a new message with our briefing, alongside all the links we can click to take us to the original blog posts and announcement articles.

Slack Message