Cross Cloud Multi Agent Comic Builder with ADK, 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 “Agent3”:

This will generate the Comic by using a multi-agent pipeline:

Once the multi Agent system is complete:

Visual Edit Agent Pipeline

The version of the ADK Deployed includes a visual builder:

Run the Online Viewer Agent

Once Agent3 has completed — go to the ADK agent selector and select “Agent4”. This agent will allow you to browse your online comic:

View the Final Artifacts

You can use Agent4 to visualize the results of the agent pipeline:

and the final panels:

Summary

The Agent Development Kit was used to visually define a multi Agent pipeline to generate comic book style HTML. 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.

Caption & Rendering Engine Upgrade

Update: Caption & Rendering Engine Upgrade

We improved the video pipeline with a focus on stability and readability:

Removed heavy zoom effects → lower memory usage
Optimized FFmpeg pipeline → fewer crashes (OOM fixes)
Introduced ASS-based captions → precise styling control
Improved contrast + font rendering for mobile

Result:
More stable rendering + significantly better readability.

new videos starts in: 8h

→ https://youtube.com/AsiaFeedTech/shorts

What OpenClaw Gets Wrong Out of the Box (And How to Fix It)

OpenClaw works well enough on a fresh install that most people don’t question the defaults. That’s the problem. The defaults are tuned for demos, not for actual sustained use, and the gap between “it ran the task” and “it ran the task correctly, securely, without silently degrading” is wider than the documentation suggests.

This post covers the specific things OpenClaw gets wrong out of the box and what you actually do about them. Not configuration trivia. The decisions that determine whether the tool is useful in a real workflow or just impressive for ten minutes.

Context Window Discipline Is Off by Default

The default OpenClaw configuration does not aggressively manage context. Tasks that involve long file reads, iterative tool calls, or multi-step pipelines will accumulate context across the session until the model starts making decisions based on stale or compressed information. By the time you notice degraded output, you’re usually several steps past where the problem started.

The model doesn’t tell you the context is degrading. It keeps working. The work just gets worse.

Fix this by setting explicit context boundaries at the task level, not the session level. Structure your task files so each subtask carries only the state it actually needs:

# task_config.yaml

context_strategy: scoped. # not persistent

max_context_tokens: 4000. # per subtask, not cumulative

context_reset_on: task_boundary

The scoped strategy forces OpenClaw to pass explicit state between subtasks instead of relying on accumulated session memory. Slower to configure. Dramatically more reliable on tasks longer than three steps.

For anything involving file analysis, add a summary step between heavy read operations and action steps. The model compresses better when you give it a structured handoff than when you let it carry raw file content forward.

The Default Timeout Behavior Silently Succeeds

Out of the box, OpenClaw will mark a task as complete if the last action it took didn’t return an explicit error. This is not the same as the task actually succeeding. A file write that fails quietly, a subprocess that exits with code 0 but produces no output, a web fetch that returns a soft redirect instead of content — all of these come back as success in the default telemetry.

You find out something went wrong when you go looking for the output and it isn’t there.

Change the completion condition to require positive confirmation, not just absence of error:

# task_config.yaml

completion_criteria:

. require_output_validation: true

. output_check: file_exists. # or: non_empty, hash_match, schema_valid

. on_ambiguous_result: retry. # not: pass

For file-producing tasks, use hash_match against an expected output signature if you’re running the same task repeatedly. For API calls, validate the response schema before marking the step done. The extra configuration takes ten minutes and eliminates an entire class of silent failures.

Filesystem Access Is Too Permissive

Default OpenClaw runs with access to your full working directory. Depending on where you cloned it, that might include dotfiles, ssh configs, environment files, or anything else sitting in your home tree. The tool doesn’t need that access to do its job, and you don’t want it to have it.

Scope the filesystem access before you run anything:

# openclaw_runtime.yaml
sandbox:
.filesystem:
.allow:
. - ./workspace/ # explicit working dir
. - ./outputs/ # explicit output target
. deny:
. - ~/.ssh/
. - ~/.gnupg/
. - ~/.config/
. - ./.* # all dotfiles
. network:
. allow: [] # block by default, add specific hosts as needed

If you’re running OpenClaw on a machine that also handles anything sensitive, put it in a separate user context or a container with explicit volume mounts. The filesystem config above helps, but process-level isolation is stronger than path-level isolation.

Network is blocked by default in the config above because most local automation tasks don’t need outbound access. Add specific endpoints only when a task explicitly requires them. If you’re uncertain, run with network disabled and see if the task completes. It usually does.

The Logging Configuration Tells You Nothing Useful

Default log verbosity in OpenClaw is set to info, which captures task start, task end, and any explicit errors. It does not capture intermediate tool calls, model reasoning steps, or the specific context state at decision points. When something goes wrong in the middle of a multi-step task, the default logs give you the beginning and the end and nothing in between.

Set verbosity to debug for any task you’re running for the first time:


# openclaw_runtime.yaml

logging:

. level: debug

. include:

. — tool_calls

. — context_state

. — reasoning_trace

. output: ./logs/openclaw_run.log

The reasoning_trace flag is the important one. It writes out the model’s intermediate reasoning before each action, which means you can actually read why it made a decision instead of reverse-engineering it from the output. On tasks that involve branching logic or conditional file operations, this is the difference between debugging in ten minutes and debugging in two hours.

Rotate logs per run, not per session. Use a timestamp in the filename:


$ openclaw run task.yaml  — log-file ./logs/run_$(date +%Y%m%d_%H%M%S).log

Old log files are how you diff behavior across runs when a task that used to work stops working.

Prompt Injection Surface Is Unaddressed

If your OpenClaw tasks involve reading external content — web pages, documents, API responses, user-provided files — the default configuration does nothing to sanitize that content before it enters the model context. A file that contains instruction-formatted text will be processed as instruction-formatted text. This is not hypothetical.

The attack surface is real any time the tool ingests content it didn’t generate itself. In a local automation context, this usually looks like a poisoned input file causing the model to write to an unintended path or skip a validation step. In a more connected setup, it looks worse.

Sanitize external content at the ingestion step, before it reaches the context:


input_handling:

. external_content:

. strip_markdown_headers: true

. truncate_at_chars: 2000

. prefix_with_role_label: “EXTERNAL_CONTENT” # signals to model this is data, not instruction

. disallow_instruction_keywords: true

The prefix_with_role_label option is the most effective single control here. Prefixing ingested content with a consistent label trains the model, over the course of a session, to treat that content as data to act on rather than instructions to follow. It doesn’t eliminate the risk but it substantially reduces it.

For anything involving untrusted input, run the task in a throwaway environment. Containers are the right answer. A Python venv and a scoped filesystem config are the second-best answer. Running it on your main machine without isolation is the wrong answer.

Task Retry Logic Compounds the Problem

When a task fails and OpenClaw retries it, the default behavior is to retry with the same context that produced the failure. If the failure was caused by a reasoning error or a corrupted context state, retrying with the same state will usually produce the same failure, or a different failure, or in the worst case a partial completion that leaves your output directory in an ambiguous state.

Disable automatic retry on first-time task runs:


# task_config.yaml

retry:

. on_failure: false. # investigate before retrying

Once you understand the failure mode, add targeted retry logic:


retry:

. on_failure: true

. max_attempts: 2

. reset_context_on_retry: true. # this is the important one

. backoff_seconds: 5

reset_context_on_retry: true clears the accumulated context before the retry attempt. Combined with scoped context strategy at the task level, this prevents failures from compounding. The task effectively starts clean, which means the retry is actually testing whether the task specification is correct, not whether the model can recover from a bad state.

None of This Is Obvious From the Documentation

The OpenClaw docs cover installation, basic task syntax, and the happy path. The failure modes, the security surface, the context degradation behavior — those are things you find out through use. Or you find them in a guide that was written by someone who already ran into all of them.

If you want the full configuration breakdown, including the automation pipeline architecture and the complete security hardening checklist, the guides are at numbpilled.gumroad.com. The automation bible and the hardening doc cover what’s in this post in considerably more depth, with worked examples for the task structures that actually hold up under real workloads.

The defaults are a starting point. Don’t run production tasks on them.

The systems behind enterprise AI adoption success – IBM

Everyone’s Buying GPUs. Almost Nobody’s Ready to Feed Them.

The enterprise AI conversation has a blind spot the size of a data center. Every budget meeting I’ve sat in over the past 18 months has the same shape: GPU allocation gets 70% of the discussion time, model selection gets 20%, and the data infrastructure that actually feeds those models gets whatever’s left over. Usually about ten minutes and a vague reference to “we’ll figure out storage later.”

This is why most enterprise AI deployments stall after the proof of concept.

The Bottleneck Nobody Budgets For

Here’s what happens in practice. A team spins up a promising AI workload — retrieval-augmented generation, a fine-tuning pipeline, an inference service. It works great on a curated dataset in a dev environment. Then they try to run it against production data at scale and everything falls apart. Not because the model is wrong, but because the storage layer can’t deliver data fast enough, the pipeline can’t unify sources across hybrid environments, and nobody planned for the I/O characteristics of AI workloads.

AI training and inference workloads have fundamentally different storage profiles than traditional enterprise applications. Training jobs need sustained sequential throughput across massive datasets. Inference needs low-latency random reads. Fine-tuning needs both, sometimes simultaneously. Your SAN that runs ERP just fine will choke on a distributed training job that’s trying to saturate eight GPUs.

IBM’s recent framing of AI-ready infrastructure gets this right: the systems layer — storage, compute fabric, automation — is where enterprise AI succeeds or dies. Not in the model layer.

The Data Gravity Problem

The reason storage matters so much for AI isn’t just throughput. It’s data gravity.

Enterprise data doesn’t live in one place. It’s spread across on-prem databases, cloud object stores, SaaS platforms, edge devices, and that one team’s PostgreSQL instance that nobody wants to touch. IBM defines enterprise AI as the integration of AI across large organizations — but integration implies the data is accessible. In most companies, it isn’t. Not in any unified, performant way.

This creates a cascading failure. Your RAG pipeline needs product data from SAP, customer interactions from Salesforce, and technical documentation from Confluence. Each source has different access patterns, different latency profiles, different security boundaries. Stitching them together with API calls and batch ETL jobs introduces hours of lag and creates brittle pipelines that break every time someone changes a schema.

The companies I’ve seen succeed at enterprise AI solve this problem first. They build a unified storage layer that can serve multiple AI workloads without requiring six different integration patterns. IBM’s approach with Storage Fusion and FlashSystem targets exactly this — high-performance, unified storage that can handle the mixed I/O profiles of AI workloads across hybrid environments. Whether you’re on their stack or not, the architectural principle holds: if your AI workloads can’t access unified data at the speed they need it, no amount of GPU spend will fix your pipeline.

Hybrid Cloud Is the Reality, Not the Exception

There’s still a persistent fantasy in some planning meetings that AI workloads will live entirely in one public cloud. Maybe someday. Right now, for regulated industries, for companies with significant on-prem investments, and for anyone who’s done the math on data egress costs, hybrid is the reality.

And hybrid AI infrastructure is hard. You need consistent orchestration across environments. You need storage tiering that can move hot data close to compute without manual intervention. You need security and governance that doesn’t collapse the moment data crosses a network boundary.

IBM identifies inadequate infrastructure as one of the top five AI adoption challenges — and in my experience, “inadequate” usually means “designed for a different era.” The infrastructure that runs your web applications, your CI/CD pipelines, your traditional analytics workloads — it wasn’t built for the throughput patterns, the data volumes, or the operational demands of production AI.

This isn’t a rip-and-replace argument. Nobody’s going to throw out their storage infrastructure overnight. But you need a plan for how your existing infrastructure evolves to support AI workloads, and that plan needs to happen before you commit to production deployments.

What Actually Works: Three Patterns From the Field

After spending time with organizations that have moved past the POC phase into production AI, I see three common patterns:

1. Storage-first capacity planning. Successful teams model their data pipeline throughput requirements before they size GPU clusters. They ask: “How fast can we feed data to training jobs?” and “What’s our p99 latency for inference-time retrieval?” If the answers don’t match the model’s appetite, they fix storage before buying more compute.

2. Unified data access across environments. Whether it’s IBM Storage Fusion, a well-architected MinIO deployment, or a managed cloud storage layer with on-prem caching, the pattern is the same: AI workloads get a single namespace to read from, regardless of where the source data physically lives. This eliminates the integration tax that kills most pipelines.

3. Automation of the data lifecycle. Production AI generates enormous amounts of intermediate data — checkpoints, embeddings, feature stores, evaluation datasets. Teams that automate tiering, retention, and cleanup avoid the “we ran out of storage on a Friday night” incident that’s practically a rite of passage.

The Uncomfortable Math

Here’s a rough calculation that sobers up most planning conversations. A mid-size enterprise running a fine-tuning pipeline on proprietary data with a 70B parameter model needs approximately 500TB of accessible, high-performance storage just for the training data, checkpoints, and model artifacts. That’s before you add your RAG corpus, your vector store, and your evaluation datasets.

Now multiply that by the number of AI initiatives in your roadmap. Most enterprises I talk to have between five and fifteen active AI projects. The storage footprint adds up fast, and it needs to perform — not just exist.

The GPU shortage got all the headlines in 2024. The storage and data infrastructure gap is the quieter crisis that will define which companies actually ship production AI in 2026.

What CTOs Should Do Next Week

Stop treating infrastructure as a downstream consequence of model selection. Flip it around.

Audit your current storage throughput against the I/O demands of your planned AI workloads. Map where your training data lives and how many network hops separate it from your compute. Calculate the real cost of your data integration layer — not just the cloud bill, but the engineering hours spent maintaining brittle pipelines.

Then have an honest conversation about whether your infrastructure roadmap matches your AI ambitions. If there’s a gap — and there almost certainly is — close it before you scale your GPU footprint. The fastest accelerator in the world is useless if it’s starving for data.

The companies that figure this out won’t just run AI. They’ll run AI that actually works in production, at scale, without the 2 AM pages. That’s a meaningful competitive advantage — and it starts with the infrastructure layer that nobody wants to talk about at the budget meeting.

A hard-earned rule from incident retrospectives:

LinkedIn Draft — Workflow (2026-03-28)

A hard-earned rule from incident retrospectives:

GitOps drift: the silent accumulation that makes clusters unmanageable

GitOps promises Git as the source of truth. The reality: every manual kubectl during an incident is a lie you told your cluster and forgot to retract.

GitOps truth gap over time:

Week 1:  Git ══════════ Cluster  (clean)
Week 4:  Git ══════╌╌╌╌ Cluster  (2 manual patches)
Week 12: Git ════╌╌╌╌╌╌╌╌╌╌╌╌╌  (drift accumulates)
                         Cluster  (unknown state)

Where it breaks:
▸ Manual patches during incidents create cluster state Git doesn’t know about — Argo/Flux will overwrite it silently.
▸ Secrets managed outside GitOps (sealed-secrets, Vault agent) drift independently — invisible in sync status.
▸ Multi-cluster setups multiply drift: each cluster diverges at its own pace once human intervention happens.

The rule I keep coming back to:
→ Treat every manual cluster change as a 5-minute loan. Commit it back to Git before the incident closes — or it’s gone.

How I sanity-check it:
▸ Argo CD drift detection dashboard — surface out-of-sync resources before they become incident contributors.
▸ Weekly diff job: live cluster state vs Git. Opens a PR for anything untracked. Makes drift visible before it’s painful.

The best platform teams I’ve seen measure success by how rarely product teams have to think about infrastructure.

Deep dive: https://neeraja-portfolio-v1.vercel.app/workflows

Curious what guardrails you’ve built around this. Drop your pattern below.

gitops #kubernetes #devops #platformengineering

I scanned 10 UK web agency websites for WCAG 2.2 violations — here’s what I found

I’m 15, and I recently built an accessibility scanner that checks websites against WCAG 2.2 standards. To stress-test it, I decided to scan the websites of 10 UK web development agencies — the companies that build websites for a living.

The setup

I ran a single-page scan (homepage only) on each agency’s site using axe-core, checking against 90+ WCAG 2.2 rules. No full crawls — just the home page.

The results were as follows:

Agency A: 8 violations (2 critical, 5 serious)
Agency B: 8 violations (2 critical, 3 serious)
Agency C: 8 violations (1 critical, 2 serious)
Agency D: 7 violations (2 critical, 2 serious)
Agency E: 5 violations (2 critical, 2 serious)
Agency F: 5 violations (0 critical, 1 serious)
Agency G: 4 violations (0 critical, 1 serious)
Agency H: 4 violations (0 critical, 1 serious)
Agency I: 2 violations (0 critical, 1 serious)
Agency J: 0 violations — clean pass

9 out of 10 failed. Only one agency had a completely clean homepage.

Totals across all 10 sites:

51 violations
9 critical
18 serious
21 moderate
3 minor

The most common violations
Colour contrast failures — showed up on 5 out of 10 sites. Text that doesn’t meet the minimum contrast ratio against its background. This is the single most common accessibility issue on the web, and agencies are no exception.
Links without discernible text — 5 out of 10 sites. Screen readers can’t tell users where a link goes if it has no accessible name. Usually caused by icon links or image links with no alt text.
Missing lang attribute — 2 out of 10 sites didn’t declare a language on their HTML element. This tells screen readers which language to use for pronunciation. Takes 10 seconds to fix.
Zooming disabled — 2 out of 10 sites used meta viewport settings that prevent users from zooming in. This is a critical violation because it blocks people with low vision from enlarging text.
Images without alt text — one agency had 272 images on their homepage with no alt text. Two hundred and seventy-two.

Why this matters

These are the companies businesses hire to build their websites. If their own sites don’t pass basic accessibility checks, what does that say about the sites they’re building for clients?

This isn’t about shaming anyone — most of these are easy fixes. But with accessibility lawsuits increasing every year (8,600+ ADA lawsuits in 2025, with ecommerce sites being the primary target), it’s worth paying attention to.
94.8% of websites fail basic accessibility checks according to WebAIM’s 2025 report. Agencies aren’t immune.

What I built

I built the scanner as a side project called Vía. It scans websites for WCAG 2.2 violations, ranks them by legal risk, explains each one in plain English, and generates AI-powered code fixes.
Not an overlay widget — actual code changes you can copy and implement.
Free to try on any site: viascan.dev
Would love to hear from other devs — do you check accessibility before shipping client work? What tools do you use? What’s missing from the current options?

I built a TOML-based task runner in Rust

demo

Every project I work on has the same problem. There’s always a set of commands I run in the same order every time, setting up dependencies, building, running checks. I got tired of either remembering them or keeping a random notes file.

Makefiles work but feel wrong outside of C projects. npm scripts are JavaScript-only. just is great but it’s another syntax to learn on top of everything else.

So I built xeq.

demo

You define named scripts in a xeq.toml file and run them with one command:

[check]
run = [
    "cargo fmt --check",
    "cargo clippy -- -D warnings",
    "cargo test"
]

[build]
run = [
    "xeq:check",
    "cargo build --release"
]
xeq run build

That’s it. No new syntax, just TOML that any project already understands.

It supports variables with fallback values, positional and named arguments, environment variables, nested script calls, parallel execution with thread control, and on_success/on_error event hooks.

The feature I’m most happy with is xeq validate, it catches undefined variables, missing nested scripts, circular dependencies, and parallel conflicts before you run anything.

There are also 30+ init templates so you can get started instantly:

xeq init rust
xeq init docker
xeq init nextjs

It works on Linux, macOS, and Windows.

Still early but functional. Would love feedback❤️

7 Mac Apps for Developers Getting Back Into Coding After a Break in 2026

Whether you took time off for burnout, a career pivot, parental leave, or just life happening — getting back into coding can feel overwhelming. The ecosystem moves fast, and your old setup probably feels stale.

I came back after a few months away recently and realized my biggest wins weren’t learning new frameworks. They were setting up the right environment so I could focus and build momentum again.

Here are 7 Mac apps that made my return to coding way smoother.

1. Raycast — Your New Command Center

Download Raycast

If you’ve been away, Raycast is what Spotlight wishes it was. It’s a launcher, clipboard manager, snippet expander, and window manager rolled into one app. The plugin ecosystem has exploded — you can search GitHub repos, manage Jira tickets, and even run AI prompts without leaving the keyboard. It cuts the friction of re-learning where everything is on your machine.

2. Warp — A Terminal That Doesn’t Punish Rust

Download Warp

Coming back to the terminal after a break used to mean staring at a blank prompt wondering what you were doing. Warp changes that — it has AI command suggestions, proper block-based output so you can actually read what happened, and built-in workflows. It’s like pair programming with your shell. If your muscle memory for CLI commands is rusty, Warp fills the gaps without making you feel dumb.

3. Obsidian — Rebuild Your Second Brain

Download Obsidian

After a break, your notes are probably scattered across three apps and a dozen browser bookmarks you forgot about. Obsidian gives you a local-first markdown vault where you can dump everything: project ideas, API references, learning notes, daily logs. The graph view helps you reconnect thoughts you’ve forgotten. I use it as my re-onboarding journal every time I come back to a project.

4. TokenBar — Know What Your AI Tools Actually Cost

Download TokenBar

Here’s something that changed while you were away: AI coding tools are everywhere now, and they all burn tokens. TokenBar ($5, lifetime) sits in your menu bar and tracks your LLM token usage across providers in real time. When you’re ramping back up and leaning heavily on Copilot, Claude, or ChatGPT to fill knowledge gaps, it’s easy to accidentally blow through $50 in a week. TokenBar keeps that visible so there are no surprises.

5. Monk Mode — Block the Feeds, Not the Apps

Download Monk Mode

The hardest part of coming back isn’t the code — it’s the distractions. Your brain wants to ease back in with Twitter, Reddit, and YouTube instead of actually writing code. Monk Mode ($15, lifetime) blocks feeds at the content level without blocking the apps themselves. You can still use YouTube for tutorials but can’t fall into the recommendation hole. It’s the guardrails you need when your discipline muscles are still warming up.

6. Rectangle — Instant Window Management

Download Rectangle

Free and open source. After a break, you probably forgot whatever window management shortcuts you used to know. Rectangle gives you keyboard shortcuts and snap zones to tile windows instantly — editor on the left, terminal on the right, docs on the second monitor. It takes 30 seconds to set up and immediately makes your workspace feel organized again. One less thing to figure out when you’re already re-learning everything else.

7. Homebrew — Get Your Dev Environment Back Fast

Download Homebrew

If you did a clean macOS install (or your old setup rotted while you were gone), Homebrew is how you get everything back in minutes instead of hours. brew install node python git gh and you’re halfway there. Pro tip: if you had a Brewfile from before, brew bundle restores your entire toolchain in one command. Future-you will thank past-you for that.

The Comeback Strategy

Coming back to code after a break isn’t about catching up on every new framework and tool that launched while you were gone. It’s about:

  1. Reducing friction — make your environment work for you, not against you
  2. Protecting focus — block the distractions before they block your progress
  3. Building momentum — ship something small in the first week, even if it’s ugly

The right tools don’t replace the work, but they make starting way less painful.

What apps helped you get back into coding? Drop them in the comments — always looking for tools that make the return smoother.

I tracked every token my AI coding agent consumed for a week. 70% was waste.

Last week Anthropic announced tighter usage limits for Claude during peak hours. My timeline exploded with developers asking why they’re hitting limits after 2-3 prompts.

I’m the developer behind vexp, a local context engine for AI coding agents. Before building it, I did something nobody seems to do: I actually measured what’s happening under the hood.

The experiment

I tracked token consumption on FastAPI v0.115.0 — the real open-source framework, ~800 Python files. Not a toy project.

7 tasks (bug fixes, features, refactors, code understanding). 3 runs per task. 42 total executions. Claude Sonnet 4.6. Full isolation between runs.

What I found

Every single prompt, Claude Code did this:

  1. Glob pattern * — found all files
  2. Glob pattern **/*.{py,js,ts,...} — found code files
  3. Read file 1
  4. Read file 2
  5. Read file 3
  6. …repeat 20+ times
  7. Finally start thinking about my actual question

Average per prompt:

  • 23 tool calls (Read/Grep/Glob)
  • ~180,000 tokens consumed
  • ~50,000 tokens actually relevant to the question
  • 70% waste rate

That 70% is why you’re hitting usage limits. You’re not asking too many questions. Your agent is reading too many files.

Why this happens

AI coding agents don’t have a map of your codebase. They don’t know which files are relevant to your question before they start reading. So they do what any new developer would do on their first day: read everything.

The difference is that a new developer reads the codebase once. Your AI agent reads it on every single prompt.

And it gets worse. As your session continues, context accumulates. By turn 15, each prompt is re-processing your full conversation history plus the codebase reads. The cost per prompt grows exponentially, not linearly.

What actually helps

Free fixes (do these today):

  1. Scope your prompts. “Fix the auth error in src/auth/login.ts” triggers 3-5 file reads. “Fix the auth error” triggers 20+.

  2. Short sessions. Start a new session for each task. Don’t do 15 things in one conversation.

  3. Use /compact before context bloats. Don’t wait for auto-compaction at 167K tokens.

  4. Audit your MCPs. Every loaded MCP server adds token overhead on every prompt, even when you don’t use it.

  5. Use /model opusplan. Planning with Opus, implementation with Sonnet.

These get you 20-30% savings. The structural fix gets you 58-74%.

What I built

The idea: instead of letting the agent explore your codebase file-by-file, pre-index the project and serve only the relevant code per query.

I built this as an MCP server called vexp. Rust binary, tree-sitter AST parsing, dependency graph, SQLite. Runs 100% locally. Your code never leaves your machine.

Here’s what changed on the FastAPI benchmark:

Metric Before After Change
Tool calls/task 23 2.3 -90%
Cost/task $0.78 $0.33 -58%
Output tokens 504 189 -63%
Task duration 170s 132s -22%

Total across 42 runs: $16.29 without vexp, $6.89 with.

The output token drop surprised me. Claude doesn’t just read less — it generates less irrelevant output too. Focused input context leads to focused responses. I didn’t design for that, but it makes sense: less noise in, less noise out.

The output quality didn’t drop. It improved.

I also ran this on SWE-bench Verified — 100 real GitHub bugs, Claude Opus 4.5, same $3 budget per task:

  • 73% pass rate (highest in the lineup)
  • $0.67/task vs $1.98 average
  • 8 bugs only vexp solved

Same model. Same budget. The only variable was context quality.

What this means for the usage limits debate

Everyone’s arguing about whether Anthropic should raise limits or lower prices. Both miss the point.

The real issue is architectural: AI coding agents don’t know your codebase. They compensate by reading everything. You pay for that compensation with tokens — and now, with tighter session limits.

Cheaper tokens help. Higher limits help. But reducing what goes into the context window in the first place is the only fix that works regardless of what Anthropic does with pricing or limits.

Full benchmark data (open source, reproducible): https://vexp.dev/benchmark

FastAPI methodology: https://www.reddit.com/r/ClaudeCode/comments/1rjra2w/i_built_a_context_engine_that_works_with_claude/

Free tier available, no account needed. I’m curious what numbers you see on your own projects — especially on repos larger than FastAPI.

Building Beautiful AI Chat UIs in Flutter: A Developer’s Guide

Building Beautiful AI Chat UIs in Flutter: A Developer’s Guide

The AI revolution has transformed how users interact with applications, and chat interfaces have become the new standard for AI-powered apps. But building a polished, production-ready chat UI in Flutter? That’s where things get tricky.

After building countless chat interfaces and seeing developers struggle with the same problems over and over, I want to share the patterns and techniques that actually work in production.

The Chat UI Challenge

Most developers underestimate chat UI complexity. It’s not just about displaying messages—you need:

  • Smooth animations for message bubbles
  • Real-time typing indicators
  • Message states (sending, delivered, failed)
  • Auto-scrolling behavior that feels natural
  • Rich content support (images, code blocks, buttons)
  • Responsive design across different screen sizes
  • Accessibility for all users

And that’s just the basics. Modern AI chat UIs also need streaming text, regeneration buttons, conversation management, and seamless integration with AI services.

The Traditional Approach (And Why It Falls Short)

Most Flutter developers start with a basic ListView and ListTile combination:

ListView.builder(
  itemCount: messages.length,
  itemBuilder: (context, index) {
    final message = messages[index];
    return ListTile(
      title: Text(message.content),
      trailing: message.isUser ? Icon(Icons.person) : Icon(Icons.smart_toy),
    );
  },
)

This works for a proof of concept, but quickly breaks down when you need:

  • Custom message bubbles with proper alignment
  • Typing animations
  • Message state management
  • Rich content rendering

You end up with hundreds of lines of custom widgets, complex state management, and a codebase that’s hard to maintain.

Enter Component Libraries: The Modern Solution

Just like how shadcn/ui revolutionized React development by providing beautiful, composable components, Flutter needs similar solutions for AI chat interfaces.

This is where component libraries specifically designed for AI chat UIs become game-changers. Instead of reinventing the wheel, you get:

  • Pre-built, tested components that handle edge cases
  • Consistent design language across your app
  • Built-in animations and micro-interactions
  • Accessibility features out of the box
  • Easy customization while maintaining quality

Building Your First AI Chat Interface

Here’s how a modern approach looks with proper component architecture:

class ChatScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChatContainer(
        messages: messages,
        messageBuilder: (message) => MessageBubble(
          content: message.content,
          isUser: message.isUser,
          timestamp: message.timestamp,
          status: message.status,
        ),
        inputBuilder: () => ChatInput(
          onSendMessage: _handleSendMessage,
          isLoading: _isGenerating,
        ),
        typingIndicator: TypingIndicator(
          isVisible: _isTyping,
        ),
      ),
    );
  }
}

Notice how clean and declarative this is? Each component has a single responsibility:

  • ChatContainer manages the overall layout and scrolling behavior
  • MessageBubble handles individual message rendering
  • ChatInput manages user input and send functionality
  • TypingIndicator shows AI processing state

Key Features to Look For

When evaluating chat UI solutions, prioritize these features:

1. Streaming Text Support

Modern AI APIs stream responses token by token. Your UI should support this natively:

MessageBubble(
  content: message.content,
  isStreaming: message.isStreaming,
  streamingCursor: true,
)

2. Rich Content Rendering

Support for code blocks, images, and interactive elements:

MessageBubble(
  content: message.content,
  contentType: message.type, // text, code, image, etc.
  actions: message.actions, // buttons, quick replies
)

3. Message State Management

Clear visual feedback for message states:

MessageBubble(
  content: message.content,
  status: MessageStatus.sending, // sending, sent, failed, retrying
  onRetry: _handleRetry,
)

4. Conversation Management

Easy handling of conversation context and history:

ChatContainer(
  conversationId: currentConversation.id,
  messages: messages,
  onLoadMore: _loadMoreHistory,
)

Performance Considerations

Chat UIs can quickly become performance bottlenecks. Here’s what to watch for:

Efficient List Rendering

Use proper list virtualization for long conversations:

ChatContainer(
  messages: messages,
  itemExtent: null, // Dynamic height
  cacheExtent: 1000, // Reasonable cache
)

Memory Management

Implement message pagination and cleanup:

// Load messages in chunks
void _loadMoreMessages() {
  if (messages.length > MAX_MESSAGES_IN_MEMORY) {
    _cleanupOldMessages();
  }
  _fetchMoreMessages();
}

Animation Performance

Use efficient animations that don’t cause jank:

MessageBubble(
  animationDuration: Duration(milliseconds: 200),
  useNativeAnimations: true,
)

Common Pitfalls to Avoid

  1. Over-animating: Too many animations create a chaotic experience
  2. Ignoring accessibility: Always test with screen readers
  3. Poor error handling: Network failures should be gracefully handled
  4. Inconsistent spacing: Maintain consistent visual rhythm
  5. Missing loading states: Users need feedback during AI processing

The Future of Flutter AI Chat UIs

The AI chat interface space is evolving rapidly. We’re seeing trends toward:

  • Multi-modal interfaces (voice, text, images)
  • Contextual actions based on message content
  • Advanced formatting for AI responses
  • Real-time collaboration features
  • Integration with vector databases for RAG applications

Getting Started

Ready to build your AI chat interface? Here are your next steps:

  1. Choose your component library carefully—look for active maintenance and good documentation
  2. Start with a simple implementation and iterate based on user feedback
  3. Test on real devices to catch performance issues early
  4. Implement proper error handling from day one
  5. Plan for internationalization if you’re targeting global users

Wrapping Up

Building great AI chat UIs in Flutter doesn’t have to be complicated. By leveraging modern component libraries and following established patterns, you can create beautiful, performant chat interfaces that users love.

The key is focusing on user experience while maintaining clean, maintainable code. Don’t reinvent the wheel—use the tools and patterns that have been proven in production.

Want to see these patterns in action? Check out the examples and dive deeper into the techniques we’ve covered. The future of Flutter development is component-driven, and AI chat interfaces are leading the way.

Building AI-powered Flutter apps? Share your experiences and challenges in the comments below. Let’s learn from each other and push the boundaries of what’s possible in mobile AI interfaces.