Introduction to Java: History, Architecture and Servers

What is Java?
Java is a high-level, object-oriented programming language used to build software like web applications, mobile apps, desktop programs, and enterprise systems.

History of Java
Java was developed by James Gosling and his team at Sun Microsystems in the early 1990s.

Key Milestones:

  • 1991 – Project started as Green Project
  • 1995 – Official release of Java
  • Originally named Oak, later renamed to Java
  • Designed for Write Once, Run Anywhere (WORA)

In 2010, Oracle Corporation acquired Sun Microsystems and now maintains Java.

Java Architecture
Java architecture is designed to be platform-independent. This is achieved using the Java Virtual Machine (JVM).

Components of Java Architecture:

1. Java Source Code

  • Written by developers in .java files

2. Java Compiler

  • Converts source code into bytecode
  • Command used: javac

3. Bytecode

  • Intermediate code (.class file)
  • Platform-independent

4. Java Virtual Machine (JVM)

  • Executes bytecode
  • Makes Java platform-independent

5. Java Runtime Environment (JRE)

  • Contains JVM + libraries

6. Java Development Kit (JDK)

  • Full package for development (JRE + tools)

Java Servers & Providers

Java applications often run on servers called Application Servers or Web Servers.

What is a Server?

A server is a computer or system that provides data, resources, or services to other computers (called clients) over a network.

Popular Java Servers:

1. Apache Tomcat
Type: Web Server
Used for running Java Servlets & JSP
Lightweight and widely used
Provided by Apache Software Foundation

2. GlassFish
Type: Application Server
Supports full Java EE features
Provided by Eclipse Foundation

3. JBoss / WildFly
Type: Application Server
Open-source and powerful
Provided by Red Hat

4. WebLogic Server
Type: Enterprise Application Server
Used in large-scale applications
Provided by Oracle Corporation

5. WebSphere
Type: Enterprise Application Server
Used in banking and enterprise systems
Provided by IBM

Switching my AI voice agent from WebSocket to WebRTC — what broke and what I learned

P2P WebRTC

Switching my AI voice agent from WebSocket to WebRTC — what broke and what I learned

A few weeks ago, I came across Darryl Ruggles’ blog post and accompanying repo for a bidirectional voice agent built with Strands BidiAgent and Amazon Nova Sonic v2. His work is remarkably well put together — I had a working voice assistant running on my laptop in about 10 minutes. The agent listens to your voice, searches a recipe knowledge base, sets cooking timers, looks up nutrition data, and converts units, all through natural conversation.

Darryl’s version uses WebSocket as the transport between the browser and the agent. It works well, but I wanted to push things further: switch the transport to WebRTC, and deploy the whole thing on Bedrock AgentCore Runtime. This post covers that journey — what changed, what broke, and what I learned along the way.

But first, a short demo!

The full source code is available on GitHub. The repo is Terraform-managed end-to-end, though you can still use Darryl’s Makefile approach if you prefer keeping Terraform for surrounding infrastructure and CLI calls for agent deployment.

Why WebRTC for a voice agent

The WebSocket version works, so why change it? A few reasons pushed me toward WebRTC.

First, latency. WebSocket runs over TCP, which means every packet is guaranteed to arrive in order. That’s great for chat messages, but for real-time audio, a single dropped packet causes the entire stream to stall while TCP retransmits. WebRTC1 uses UDP under the hood — if a packet is lost, the stream keeps going. For a voice conversation, a tiny glitch is far better than a noticeable pause.

Second, the browser does more of the heavy lifting. With WebSocket, I had to capture microphone audio using getUserMedia, downsample it to 16kHz with a ScriptProcessorNode, encode it as base64 PCM, and send it as JSON messages. On the playback side, I needed an AudioWorklet with a ring buffer to handle the incoming audio stream. With WebRTC, the browser handles audio capture, encoding (Opus), and playback natively through RTCPeerConnection. The frontend code got significantly simpler.

Third, WebRTC is future-proof for video. AI avatar are getting there with acceptable latency, and WebRTC handles video tracks just as naturally as audio tracks. Adding a video stream later is just a matter of adding a track to the existing peer connection — no architectural change needed.

A quick primer on WebRTC architectures

There are two fundamentally different ways to use WebRTC, and the choice matters when building a voice agent.

Peer-to-peer (P2P)

In P2P WebRTC, two peers connect directly to each other. There’s no media server in the middle — audio flows straight from the browser to the agent and back. A TURN2 relay server may be needed when one or both peers are behind NAT3 (which is almost always the case in production: clients are behind Internet Router and Agents need to be in private VPC to access company tools), but the TURN server just forwards packets without inspecting or processing them.

P2P WebRTC

Room-based (SFU)

In a room-based architecture, a media server (called an SFU4 — Selective Forwarding Unit) sits in the middle. Participants connect to the server, not to each other. The server receives audio/video tracks from each participant and selectively forwards them to the others. LiveKit, Amazon Chime SDK, and Daily are examples of SFU-based platforms.

Media Server WebRTC

For a 1:1 voice agent, P2P is simpler and avoids the cost and complexity of running (or paying for) a media server. I went with P2P using Amazon Kinesis Video Streams (KVS) as the managed TURN relay — this is the documented approach for WebRTC on AgentCore.

I did consider room-based solutions, but each SFU platform requires its own SDK — you can’t just connect with a standard RTCPeerConnection. AWS’s own WebRTC offering, Amazon Chime SDK, is feature-rich (transcription, recording, analytics) and significantly cheaper than alternatives like LiveKit or Daily, but it doesn’t yet offer a paved path for server-side agent-to-room communication. That’s a feature I’d love to see, given how compelling the rest of the Chime SDK is. For now, P2P with KVS TURN was the most straightforward path. I’ll definitely consider in-room WebRTC, but that’s a story for another post.

The WebRTC stack: browser and server

On the browser side, WebRTC is built in. The RTCPeerConnection API is available natively in every modern browser — Chrome, Safari, Firefox, Edge. You create a peer connection, add a microphone track via getUserMedia, and the browser handles audio encoding (Opus), ICE candidate gathering, and DTLS encryption. No libraries needed.

On the server side, it’s a different story. WebRTC was designed for browsers, not for Python backends. The go-to library for server-side WebRTC in Python is aiortc — an asyncio-based implementation of WebRTC and ORTC. It handles peer connections, ICE negotiation, and media tracks, and uses PyAV (FFmpeg bindings) for audio/video frame processing. It’s not as battle-tested as browser WebRTC, but it works well and is what the AWS sample code uses too.

Architecture: local development vs. deployed

One thing I wanted to preserve from Darryl’s original design is the ability to run everything locally for development, without any cloud infrastructure. The WebRTC migration maintains this.

Local mode

In local mode, the agent runs on your machine. The browser and agent are on the same network (or the same machine), so WebRTC connects peer-to-peer without needing a TURN relay. Signaling — the exchange of SDP5 offers/answers and ICE6 candidates — goes through the Vite dev server proxy to the local FastAPI server.

Local mode

Deployed mode

In deployed mode, the agent runs inside a Docker container on Bedrock AgentCore Runtime, attached to a VPC via an elastic network interface (ENI) in a private subnet. The browser can’t reach the agent directly — all media traffic flows through a KVS TURN relay. Signaling goes through AgentCore’s /invocations HTTP endpoint, authenticated with SigV4 via the @aws-sdk/client-bedrock-agentcore SDK.

Voice Agent deployed on AgentCore

The following diagram from the AWS documentation shows how it works in terms of networking: signaling flows through AgentCore’s HTTP endpoint while media traffic goes through the VPC’s NAT gateway to the KVS TURN relay:

AgentCore WebRTC Architecture

The important thing to note is that the agent code is almost identical between local and deployed modes. The BidiAgent, BidiNovaSonicModel, and all four tools (recipe search, timer, nutrition lookup, unit converter) are completely unchanged. The only difference is the transport layer: in local mode, aiortc connects P2P; in deployed mode, it connects through KVS TURN. The agent detects which mode it’s in via the CONTAINER_ENV environment variable and configures ICE servers accordingly.

This clean separation was possible because of Strands’ BidiInput/BidiOutput protocol. I wrote two small adapter classes — WebRTCBidiInput and WebRTCBidiOutput — that bridge aiortc audio tracks to the event format BidiAgent expects. The agent doesn’t know or care whether audio is coming from a WebSocket or a WebRTC track.

What Bedrock AgentCore’s WebRTC support adds

On March 20th, 2026, AWS announced WebRTC support for AgentCore Runtime.

I’m not 100% sure, and am ready to stand corrected, but my impression is that the building blocks — VPC network mode, KVS TURN, the /invocations HTTP endpoint — all existed before this announcement. VPC network mode has been available since AgentCore’s general availability in October 2025. KVS TURN is a long-standing Kinesis Video Streams feature. And /invocations has always been the standard HTTP endpoint for AgentCore runtimes.

What the March 20th release adds, as far as I can tell, is official documentation, working sample code, and the explicit statement that WebRTC is a supported protocol on AgentCore Runtime. Before this, you could technically have assembled the same pieces yourself, but you’d be on your own — no docs, no samples, no guarantee it would keep working.

What AgentCore does provide is genuinely valuable: managed container hosting with auto-scaling, session isolation between concurrent users, built-in observability (CloudWatch logs, X-Ray traces), and no infrastructure to manage beyond the VPC. I didn’t have to set up ECS, configure load balancers, or manage container orchestration.

That said, there’s a fair amount of custom code involved. The WebRTC signaling (SDP exchange, ICE candidate management), the aiortc peer connection lifecycle, the audio track bridging to BidiAgent, and the KVS TURN credential management — all of that is application code that I wrote. AgentCore hosts and runs it, but doesn’t abstract it away.

Challenges and lessons learned

The migration from WebSocket to WebRTC started as a smooth ride (local mode worked on first attempt!), and was not so smooth afterwards, as I tried to get it to work on Bedrock AgentCore. Here’s what tripped me up.

VPC availability zone compatibility

AgentCore Runtime only supports specific availability zones. In us-east-1, only use1-az4 (us-east-1a), use1-az1 (us-east-1c), and use1-az2 (us-east-1d) are supported. I initially let Terraform pick the first two AZs automatically, which gave me us-east-1a and us-east-1b. The runtime update failed with a cryptic UPDATE_FAILED status. The actual error message — mentioning the unsupported AZ — was buried in the failureReason field of the API response, not surfaced in the Terraform error. I ended up hardcoding the supported AZs in my VPC module.

Session affinity

This one cost me hours. WebRTC signaling is a multi-step handshake — the browser and agent exchange several messages to establish a connection. The agent needs to remember the connection state from the first message when processing the second and third. If those messages land on different server instances, the agent has no memory of the ongoing handshake and the connection fails.

I initially used raw SigV4-signed HTTP POST requests, assuming that including the session ID as a query parameter would provide routing affinity. It didn’t. The ICE candidates were landing on a different container instance (?) than the one holding the peer connection.

The fix was to use the @aws-sdk/client-bedrock-agentcore SDK with InvokeAgentRuntimeCommand and the runtimeSessionId parameter. This is the only reliable way to ensure all requests for a WebRTC session reach the same container instance. The AWS sample code uses this pattern too — I just didn’t notice it at first because I was focused on the WebRTC parts.

SDP candidate filtering

When the agent creates a peer connection inside the VPC, aiortc generates ICE candidates for all available network interfaces — including VPC-internal IPs like 169.254.0.2. These host candidates end up in the SDP answer sent to the browser. The browser dutifully tries to connect to them, fails (because they’re unreachable from the public internet), and only then falls back to the relay candidates. This adds several seconds to the connection time.

The fix is straightforward: strip non-relay candidates from the SDP answer before returning it to the browser. In deployed mode, the only candidates that can work are TURN relay candidates, so there’s no reason to include the others.

TURN-only mode

Similar to the SDP filtering issue, the agent’s aiortc instance tries host candidates before relay candidates by default. Since host candidates use VPC-internal IPs that can never work from the browser’s perspective, this wastes time. Configuring aiortc to only use TURN relay candidates (turn_only=True) skips straight to the candidates that actually work.

Lazy KVS initialization

I initially called kvs.init() at module import time, guarded by an if IS_CONTAINER check. This worked fine locally but caused the container to crash on AgentCore. The KVS API call to find or create the signaling channel requires AWS credentials, and during container startup there can be a brief delay before the IAM role credentials are available. Moving the initialization to the first actual request (lazy init) fixed the crash.

Cold start behavior

After the container has been idle for a while, the first WebRTC connection attempt sometimes fails. The signaling requests succeed (AgentCore returns 200), but the ICE connection never completes. I suspect this is related to AgentCore spinning up a fresh container instance — the first few requests may be handled by an instance that isn’t fully warmed up. On the agent side, I explicitly set --workers 1 in the uvicorn command to ensure all requests within a container hit the same process (and therefore the same in-memory peer connection state). On the frontend, I added a retry mechanism: wait for ICE to reach “connected” state, and if it doesn’t within 10 seconds, tear down and retry with a new session ID. Together, these made the connection reliable.

Key code

I won’t walk through every file, but here are the pieces that make the WebRTC integration work.

The WebRTCBidiInput adapter reads audio frames from the aiortc track, resamples them to 16kHz, and returns them as bidi_audio_input events that BidiAgent understands:

class WebRTCBidiInput:
    def __init__(self, track):
        self._track = track

    async def __call__(self):
        try:
            frame = await self._track.recv()
        except MediaStreamError:
            raise StopAsyncIteration
        resampled = _resampler.resample(frame)
        pcm = b"".join(f.planes[0] for f in resampled)
        return {
            "type": "bidi_audio_input",
            "audio": base64.b64encode(pcm).decode("utf-8"),
            "sample_rate": 16000,
        }

The WebRTCBidiOutput adapter does the reverse — it receives events from BidiAgent and pushes audio to the aiortc output track:

class WebRTCBidiOutput:
    def __init__(self, output_track):
        self._output_track = output_track

    async def __call__(self, event):
        if event.get("type") == "bidi_audio_stream":
            audio_bytes = base64.b64decode(event["audio"])
            self._output_track.add_audio(audio_bytes)
        elif event.get("type") == "bidi_interruption":
            self._output_track.clear()

On the frontend, the useWebRTCSession hook uses the AgentCore SDK for signaling:

const invoke = async (action, data = {}) => {
  const client = new BedrockAgentCoreClient({ region, credentials });
  const resp = await client.send(new InvokeAgentRuntimeCommand({
    agentRuntimeArn,
    runtimeSessionId: sessionId,  // ensures session affinity
    contentType: 'application/json',
    payload: new TextEncoder().encode(JSON.stringify({ action, data })),
  }));
  return JSON.parse(new TextDecoder().decode(
    await resp.response.transformToByteArray()
  ));
};

The full source is in the repo — the feat/webrtc branch has the local-only version, and feat/webrtc-agentcore has the full deployed version with Terraform.

Development tooling

I built this project using Kiro CLI, Amazon’s AI development assistant. It handled the planning, code generation, debugging, and iterative deployment — including the many rounds of trial-and-error with WebRTC configuration that this post describes. The back-and-forth between writing code, deploying, checking logs, and fixing issues was a natural fit for an AI pair-programming workflow.

Try it yourself

To run locally:

git clone https://github.com/psantus/strands-bidir-nova.git
cd strands-bidir-nova
git checkout feat/webrtc
uv sync && make install-frontend
# Terminal 1:
make serve
# Terminal 2:
make serve-frontend

Open http://localhost:5173, click the microphone, and start talking.

For the deployed version on AgentCore, check out the feat/webrtc-agentcore branch and follow the README. You’ll need a Bedrock Knowledge Base with some recipes, a Cognito user pool, and Docker for building the container image. A single terraform apply handles the rest.

If you’d rather start with the WebSocket version first, Darryl Ruggles’ original post is the place to go.

Paul Santus is an independent cloud consultant at TerraCloud. He helps organizations build and deploy AI-powered applications on AWS. Connect with him on LinkedIn.

  1. WebRTC (Web Real-Time Communication) — An open standard for real-time audio, video, and data communication directly between browsers and devices, using UDP-based transport. ↩

  2. TURN (Traversal Using Relays around NAT) — A relay server that forwards media traffic when two peers can’t connect directly. Both sides send their audio to the TURN server, which relays it to the other side. ↩

  3. NAT (Network Address Translation) — A networking mechanism that maps private IP addresses to public ones. Most home routers and cloud VPCs use NAT, which prevents direct inbound connections. ↩

  4. SFU (Selective Forwarding Unit) — A media server that receives audio/video tracks from participants and selectively forwards them to others, without mixing or transcoding. Used by LiveKit, Chime SDK, Daily, etc. ↩

  5. SDP (Session Description Protocol) — A text format describing a multimedia session: codecs, transport addresses, and media types. In WebRTC, peers exchange SDP “offers” and “answers” to negotiate the connection. ↩

  6. ICE (Interactive Connectivity Establishment) — A protocol for finding the best network path between two peers. It gathers candidate addresses (local, server-reflexive, relay) and tests connectivity between them. ↩

Some problems in Looping.

1.Multiples of 3 and 5
Code:
let i=1;
while(i<50){
if(i%3==0 && i%5==0){
console.log(i);
}
i++;
}
What the code is doing:

  1. – It Start from i = 1
  2. – It Will Loop until i < 50
  3. – Check if i is divisible by both 3 and 5
  4. – If yes → Then it will be printed
  5. – Increase i every time

Screenshot of the Program:

2.Prime Number
code:
let count=0;
let num=5;
let i=1;
while(i<=num){
if(num%i==0){
count++;
}
i++
}
if(count==2){
console.log(“It is a PrimeNumber”)
}else{
console.log(“It not a PrimeNumber”)
}

What the code is doing:

  1. – Start with i = 1
  2. – Loop until i <= num
  3. – Check if num is divisible by i
  4. – If yes → increase count
  5. – Increase i every time
  6. – After loop ends:
    – If count == 2 → print Prime Number
    – Else → print Not a Prime Number

Screenshot of the Program:

3.count of digits
Code:
let num=12345;
let count=0;
while(num>0){
num= Math.floor(num/10);
count++;
}
console.log(count)

What the code is doing:

  1. – Start with num = 12345
  2. – Start with count = 0
  3. – Loop until num > 0
  4. – Inside the loop:
    – Remove the last digit of num → num = Math.floor(num / 10)
    – Increase count by 1
  5. – Repeat the loop
  6. – After the loop ends → print count

Screenshot of the Program:

🔄 GBase Database Internals: Transactions, Logging, and Service Lifecycle Explained

In production systems, executing SQL is only part of the story.

Behind every operation in GBase database, there is a full lifecycle:

  1. A transaction begins
  2. Data changes are written
  3. Logs record every step
  4. The database service ensures durability
  5. Recovery is possible if something fails

This article connects transactions + logging + service management into one complete picture.

🧠 Part 1: Transactions — The Foundation of Data Consistency

In GBase, all data operations are executed inside transactions.

Example:

BEGIN;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;

What happens:

  • Transaction starts
  • Data is modified
  • Logs capture the change
  • Commit finalizes the operation

GBase ensures ACID compliance, meaning operations are atomic and recoverable.

🧱 Part 2: Logging — How GBase Tracks Every Change

GBase uses logical and physical logs to track database activity.

🔹 Logical Logs

  • Record SQL-level operations
  • Track transactions (INSERT / UPDATE / DELETE)

🔹 Physical Logs

  • Record low-level storage changes
  • Track page modifications

You can monitor logs with:

onstat -l

This shows:

  • Log usage
  • Number of records
  • Space consumption (gbasedbt.com)

📊 Example Log Output

numrecs    Log Space used
82067      12130972

This indicates how many operations have been recorded and how much space they consume (gbase.cn)

🔄 What Happens During an UPDATE?

UPDATE users SET age = 30 WHERE id = 1;

Internally:

  1. Transaction begins
  2. Old data is logged
  3. New data is written
  4. Log entry is created
  5. Transaction commits

👉 If something fails, logs allow rollback or recovery.

⚙️ Part 3: Database Service Lifecycle

Transactions and logs rely on a running database engine.

🚀 Start Database

oninit -vy

This:

  • Allocates shared memory
  • Loads configuration
  • Initializes logs and storage
  • Starts background processes (gbase.cn)

🛑 Stop Database

onmode -ky

This:

  • Terminates sessions
  • Flushes logs to disk
  • Ensures safe shutdown (gbasedbt.com)

🔍 Check Status

onstat -

Example:

On-Line -- Up 00:00:14

Shows database is running normally (gbase.cn)

🔄 Part 4: Recovery — When Things Go Wrong

Scenario: System Crash

If the database crashes:

  1. Restart with oninit
  2. GBase reads logs
  3. Applies committed transactions
  4. Rolls back incomplete ones

During startup, GBase initializes:

  • Log/checkpoint information
  • Storage structures
  • Recovery processes (gbase.cn)

Example Error During Startup

Cannot open chunk '/path/datadbs1_1'

This indicates storage issues, but logs still help recover valid data (gbase.cn)

📊 Part 5: Monitoring Transactions and Logs

After heavy operations, monitoring is critical.

Check Logs

onstat -l

Check System Performance

onstat -p

Shows:

Check Sessions

onstat -g ses

Displays active transactions and connections (gbase.cn)

⚠️ Real-World Issues

1. Heavy Logging

Large updates:

UPDATE orders SET status = 'done';

May cause:

  • High disk I/O
  • Log space exhaustion

2. Lock Contention

Transactions can lock rows or tables:

  • Blocking other queries
  • Causing performance issues (DEV Community)

3. Startup Failures

Incorrect configuration or missing files can prevent startup.

⚡ Best Practices

✅ Use Transactions Explicitly

BEGIN;
UPDATE ...
COMMIT;

✅ Monitor Logs Regularly

onstat -l

✅ Ensure Proper Shutdown

onmode -ky

Avoid force killing processes.

✅ Watch System After Heavy Operations

onstat -p

🧠 Key Insight

GBase reliability comes from the combination of three layers:

Layer Role
Transaction Controls data changes
Logging Records every operation
Service Ensures execution & recovery

👉 You need all three to work together.

🚀 Final Thoughts

GBase is not just executing SQL—it is constantly:

  • Tracking changes through logs
  • Managing transactions
  • Maintaining system state
  • Ensuring recovery

Understanding this lifecycle helps you:

👉 Debug issues faster
👉 Prevent data loss
👉 Optimize performance

💬 Key Takeaways

  • Every data change is logged
  • Transactions ensure consistency
  • Logs enable recovery
  • Service lifecycle controls everything

🔥 What to Try Next

  • Run an UPDATE and observe logs with onstat -l
  • Restart the database and observe recovery
  • Simulate rollback scenarios

If you want, I can next generate:

  • 🧪 A hands-on lab (simulate crash + recovery step-by-step)
  • 🔍 A deep dive into GBase logging internals
  • ⚡ Or a performance tuning guide for transactions

Porting Test Drive II from SNES to PC, Part 36: Closing the SNES organic default-rival handoff path

SNES Select Opponent BG1 surface

Porting Test Drive II from SNES to PC, Part 36: Closing the SNES organic default-rival handoff path

The last front-end checkpoint proved two things separately:

  • the downstream Select Opponent screen is a real 2x2 surface on $1C70
  • the bottom-right clock slot already changes handoff fields at L008B87

What was still missing was the live corridor.

Did the default rival path actually reach the gameplay callback family
organically, or were we only seeing that by forcing callbacks in headless
tests?

That gap is now closed.

The useful distinction was no longer menu meaning

By the previous checkpoint, the menu semantics were already narrow:

  • $1C70 = 0..2 means rival-enabled handoff
  • $1C70 = 3 means no-opponent handoff

The remaining uncertainty was runtime reachability.

So this checkpoint stopped treating the problem as a UI question and treated it
as a timing question instead.

The fourth slot is still important, but now as an input-timing target, not as
an unresolved visual.

SNES Select Opponent BG1 surface

SNES downstream selection-box row 0x1D

Two no-force probes now recover the same live corridor

I ran two timed-input boot probes without pinning active_main.

Both runs recover the same organic path:

L00C20B -> 01:C1D2 -> L00BE76 -> L008B87 -> 01:902D

and then, later:

active_main = 02:9016

The two runs land at slightly different frame numbers, but they agree on the
important part:

  • the Select Opponent loop really does fall through into the known gameplay
    callback family
  • the default top-left rival slot is enough to reach that corridor
  • this no longer depends on forced callback injection

What the probes actually proved

Run v1b reaches:

  • L00C20B at frame 1562
  • first 01:C1D2 at frame 1577
  • L00BE76 at frame 1616
  • L008B87 at frame 1706
  • 01:902D at frame 1857
  • first active_main = 02:9016 at frame 2014

Run v2 reaches:

  • L00C20B at frame 1584
  • first 01:C1D2 at frame 1599
  • L00BE76 at frame 1646
  • L008B87 at frame 1736
  • 01:902D at frame 1887
  • first active_main = 02:9016 at frame 2044

In both runs:

  • $1C70 stays 0
  • $1C76 flips from 0 to 1 only after L008B87

So these are not clock-slot runs.

They are the recovered default-rival baseline.

The remaining blocker is narrower now

This is the real value of the checkpoint:

the blocker is no longer “can this front-end path reach gameplay organically?”

That answer is now yes.

The blocker is now:

can I move the input to the bottom-right clock slot after 01:C1D2 is
already live, so $1C70 actually leaves 0 before the same corridor falls
through L008B87?

That is a much better next target.

The next probe is no longer generic.

It is specific:

  • inject right+down only inside the live 01:C1D2 window
  • keep the already recovered default-rival run as the control case
  • compare the later $1C76 = 0 path against the now-closed $1C76 = 1
    baseline

The LiteLLM Supply Chain Attack: A Wake-Up Call for AI Infrastructure

A routine dependency install triggered one of the most serious supply chain incidents in the AI ecosystem. A compromised release of LiteLLM, an AI gateway with about 97 million monthly downloads, introduced malicious code that quietly extracted sensitive credentials from developers’ systems. The attack needed no explicit action. Simply installing the affected package was enough to begin data exfiltration.

What makes this significant is how it happened and what it exposed. The breach began upstream in the software supply chain and exploited trust in CI/CD pipelines and dependency systems. It did not go after users directly. Even well-secured environments were affected by normal development workflows.

The scale of concern became clear when voices like Andrej Karpathy, former director of Tesla AI and former research scientist at OpenAI, pointed out how dangerous supply chain attacks have become, with Elon Musk reinforcing the need for caution.


source: https://x.com/karpathy/status/2036487306585268612?s=20

This points to a deeper issue in how modern AI infrastructure is built and trusted. In this article, let us examine what happened, how the attack unfolded, and what it means for building safer AI systems going forward.

Incident Overview: Scope, Timeline, and Entry Point

The issue began days before the public release. On March 19, the attacker group TeamPCP changed Git tags in the Trivy GitHub Action to point to a malicious build that carried a credential harvester.

Trivy runs deep within many CI/CD pipelines, including LiteLLM. It became a quiet but effective entry point. On March 23, a similar pattern appeared in Checkmarx KICS and the domain models.litellm.cloud was registered just before the main event.

On March 24 at 10:39 UTC, LiteLLM’s CI/CD pipeline ran the compromised Trivy scanner without version pinning. That gap exposed the PyPI publish token from the GitHub Actions environment. Within hours, two malicious versions were released and stayed available until about 16:00 UTC.

  • Version 1.82.7: Malicious code placed in proxy_server.py, triggered on import
  • Version 1.82.8: A .pth file that runs on every Python startup, no import needed
pip install litellm

A simple install like this could pull sensitive data such as SSH keys, cloud credentials, API keys, and more. The data was encrypted and quietly sent out. The breach came to light only after a bug in the attacker’s code caused a system crash, exposing activity that would have stayed hidden through a transitive dependency.

How the Attack Moved Through Trusted Systems

The attack began outside LiteLLM and moved through trusted systems until it reached developers.

  • Attackers changed the Trivy GitHub Action and pointed it to a malicious version
  • LiteLLM’s CI CD pipeline used it without fixing a specific version
  • The scanner pulled the PyPI publishing token from the pipeline
  • Attackers used this access to release malicious LiteLLM versions
  • Developers installed or updated the package and received the compromised code

Each step depended on trust. The pipeline relied on the scanner, and developers relied on the package source. Nothing seemed unusual at each step.

The attack spread through everyday workflows, including indirect installs through dependencies. Many systems were exposed, and developers remained unaware. This points to a clear issue. Risk extends beyond application code and includes the tools and systems that support it.

Understanding the True Impact of the Breach

The impact of this attack was severe. A simple install gave access to sensitive data across developer systems.

Attackers could access SSH keys, cloud credentials, Kubernetes secrets, API keys, CI CD tokens, and database passwords. Shell history, git credentials, and crypto wallets were also exposed .

This data was then encrypted and sent to an external domain. The process ran quietly in the background and gave no clear signal to the user. If Kubernetes access was present, the attack extended further. It could read cluster secrets and create privileged workloads to maintain access inside the system.

This level of access goes beyond a single application. It opens the door to full control of infrastructure across environments. AI systems increase this risk further. Tools like LiteLLM act as a central layer for multiple provider keys and requests. Once exposed, the impact spreads across connected services.

What This Reveals About Modern AI Systems

This event highlights key patterns in how modern AI systems are built and where risk enters.

  • Deep Dependency Chains: Modern AI stacks rely on multiple external packages layered atop one another. Many dependencies are indirect, which makes them harder to track. A single weak link can affect the entire system.
  • Trust Across Layers: CI CD pipelines trust external tools. Build systems’ trust dependencies. Developers trust package registries. Each layer depends on the next, creating a chain that attackers can move through step by step.
  • Centralized Gateway Design: AI gateways such as LiteLLM collect keys from multiple providers and route all requests through a single layer. This improves convenience but increases risk. Exposure at this layer affects every connected service.
  • Limited Visibility: Teams often lack clear insight into what runs inside pipelines or which dependencies are pulled in indirectly. This reduces early detection and slows response.
  • Security Scope Gaps: Security efforts often focus on application code. Supporting systems such as pipelines, tools, and dependencies receive less attention, even though they carry equal risk.

The Risk Built into Modern AI Architectures

The earlier points highlight a gap. Deep dependency chains, layered trust across systems, centralized gateways, and limited visibility increase risk across AI infrastructure.

Core functions like API routing and key handling often rely on large dependency chains. As these layers grow, exposure increases. The same structure that supports speed also creates entry points for compromise. Addressing this requires architectural changes, including reducing exposure to dependencies, isolating critical components, and maintaining tighter control over execution environments.

AI gateways sit at a critical layer, handling traffic, managing keys, and connecting providers, which makes their design directly tied to system risk.

Building Low-Exposure AI Infrastructure with Bifrost

For teams evaluating options right now, the focus should be on how a gateway limits the impact of a compromised dependency. This depends on how the system is designed and where control is maintained across it.

Bifrost is an open-source AI gateway that routes and manages requests across 20+ model providers through a single interface. Keys, traffic, and access stay under your control.

Its design takes a different path at the architectural level. The PyPI attack surface is removed entirely. Built in Go, Bifrost runs as a single binary or container, which removes the need for pip installs, avoids a Python runtime inside the gateway, and eliminates dependency chains that can be altered during installation.

Control remains within the user’s environment because Bifrost runs on private networks, where traffic and credentials remain internal. Key management is handled through direct integration with systems such as HashiCorp Vault, AWS Secrets Manager, GCP, and Azure, where secrets stay in managed storage with controlled access and clear audit visibility.

Comparative Overview: LiteLLM vs. Bifrost

The differences become clearer when both approaches are viewed side by side across key areas of risk, control, and deployment.

Category LiteLLM Bifrost
Language Python (PyPI) Go (binary/Docker)
Supply chain exposure High Eliminated (no pip dependency)
Credential storage Environment/config-based Vault-integrated
Deployment model Application-level In-VPC, isolated
Audit capability Limited Immutable, SIEM-ready
Migration effort — One-line configuration change
Performance — ~11 µs overhead at scale
Compatibility Native Full LiteLLM compatibility

Closing Thoughts

The LiteLLM incident leaves a lasting takeaway. Systems built on layers of trust, dependencies, and software supply chains carry risk that spreads faster than expected. AI gateways sit at the center of this setup, which makes their design choices critical to overall security.

This is a moment to rethink how these systems are structured. Reducing dependency exposure, separating critical components, and maintaining control within your own environment can limit how far an issue can travel across the supply chain.

Bifrost follows this direction. Its design reduces exposure to dependencies, keeps credentials within managed systems, and runs in controlled environments, which helps limit the spread of similar attacks. To see how this approach can be applied, explore the Bifrost documentation.

If you do not know what each dependency in your AI stack has access to, that is the first thing to fix.

Self-Hosted Stripe Billing Anomaly Detection with FastAPI

Self-Hosted Stripe Billing Anomaly Detection with FastAPI

Billing anomalies are silent killers. Failed charges that go unnoticed, webhook delivery gaps, sudden MRR drops at 2 AM — by the time you see them in your dashboard, the customer is already gone.

In this post I’ll walk through BillingWatch — a self-hosted FastAPI service that listens to Stripe webhooks and fires alerts the moment something looks wrong.

The Problem

Stripe’s own dashboard is reactive. You find out about billing issues after they’ve happened, often hours later. What you want is a system that:

  • Detects failed charges in real time
  • Spots unusual refund spikes
  • Alerts when subscription churn exceeds baseline
  • Runs on your infrastructure (no SaaS subscription)

Architecture

Stripe Webhooks → FastAPI /webhook → Anomaly Engine → Alerts (WhatsApp / Slack / email)

The service is intentionally minimal — a single FastAPI app, SQLite for event storage, and a threshold-based anomaly detector.

Webhook Handler

from fastapi import FastAPI, Request, HTTPException
import stripe, os

app = FastAPI()
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
WEBHOOK_SECRET = os.environ["STRIPE_WEBHOOK_SECRET"]

@app.post("/webhook")
async def stripe_webhook(request: Request):
    payload = await request.body()
    sig = request.headers.get("stripe-signature", "")

    try:
        event = stripe.Webhook.construct_event(payload, sig, WEBHOOK_SECRET)
    except stripe.error.SignatureVerificationError:
        raise HTTPException(status_code=400, detail="Invalid signature")

    if event["type"] == "invoice.payment_failed":
        await handle_payment_failure(event["data"]["object"])

    return {"status": "ok"}

Anomaly Detection

The anomaly engine compares current event rates against a rolling 7-day baseline:

def is_anomalous(event_type: str, window_hours: int = 1) -> bool:
    recent = count_events(event_type, hours=window_hours)
    baseline = avg_events_per_hour(event_type, days=7)

    # Alert if rate is 3x above baseline
    return recent > (baseline * 3 + 1)

This catches spikes in refunds, failed charges, and subscription cancellations without requiring ML infrastructure.

Running It

git clone https://github.com/rmbell09-lang/BillingWatch
cd BillingWatch
pip install -r requirements.txt
export STRIPE_SECRET_KEY=sk_live_...
export STRIPE_WEBHOOK_SECRET=whsec_...
uvicorn main:app --host 0.0.0.0 --port 8000

Then point your Stripe webhook dashboard at https://yourdomain.com/webhook.

Why Self-Hosted?

No per-seat pricing, no SaaS lock-in, full control over your alert logic. For indie developers and small teams watching burn rate, that matters.

Full source, Docker setup, and deployment docs on GitHub.

OWASP Top 10 – A05: Security Misconfiguration (Remediation Perspective)

As I have been working with OWASP Top 10, so far I have studied A01 to A04 and performed remediations according to them on my projects, so I can have an idea of security and standard testing of my web apps. In this article, I would like to tell you about my work regarding A05, which is Security Misconfiguration.

t is observed by me that many of the aspects addressed in one OWASP category can also be addressed in more than one category. So this is more about discipline while developing a web app.

In my work on OWASP A05, I performed the following remediations and improvements:

Note: As some aspects are also addressed in more than one OWASP category, my work is more likely inclined toward one specific category in this article.

Environment Configuration

One of the aspects that many beginner developers miss out on—and exhibit work-shyness in—is not preparing separate environments for:

  • Development (Project Making)
  • Local (Running on a local closed network)
  • Production (Deploying the project as delivered and ready to meet the internet)

This configuration can be done by preparing .env files accordingly and editing them for the type of project start.

**For example: **It is best and advised to have this variable:

NODE_ENV=production

The variable value can be changed according to the project environment like “dev” for development, “local” for local, and “prod” for production.

This allows necessary condition checks and flag triggering such as:

if (process.env.NODE_ENV === "production") {
  app.set("trust proxy", 1);
}

This allows IP address logging and other rate-limiting and security methods to be applied.

Note: Proxy must be trusted with this flag only if you are using NGINX or another trusted reverse proxy like Cloudflare or reputed hosting services.

Secure HTTP Headers

Another security misconfiguration that must be handled is the use of Helmet for securing HTTP headers.

import helmet from "helmet";

app.use(helmet());

This adds:

  • Content-Security-Policy
  • XSS Protection
  • Frameguard
  • HSTS

CORS Configuration

Another important configuration is CORS:

app.use(cors({
  origin: ["https://yourfrontend.com"],
  credentials: true
}));

One should regularly identify their domains and use the correct URLs in their CORS configuration.

Hide Sensitive Errors

Many developers forward backend errors to the frontend for quick debugging, like:

res.status(500).json({ error: err });

This is not advised, as it may expose backend structure, repositories, or system limitations.

Instead, use standard messages such as:

res.status(500).json({ message: "Internal Server Error" });

For debugging, use server logs:

try {
  // Logic
} catch (error) {
  console.error("Error While Processing In (Endpoint)", error);
  res.status(500).json({ message: "Internal Server Error" });
}

Disable Stack Traces & Info Leaks

Information about backend resources matters a lot. It allows attackers to guess weaknesses and evaluate your servers.

We can use:

app.disable("x-powered-by");

This hides:

  • X-Powered-By: Express

Enforce HTTPS

When using hosting services, they often handle HTTPS automatically.

But when deploying manually using a reverse proxy like NGINX, ensure:

  • NGINX + Certbot setup
  • Redirect HTTP → HTTPS
  • Install SSL certificate

Usually, this is part of NGINX deployment.

Secure Cookies

While setting cookies from the backend, ensure the following:

res.cookie("token", token, {
  httpOnly: true,
  secure: true,
  sameSite: "strict"
});

Remove Default Credentials

During development, many developers use default credentials for quick testing.

Removing these from the database is very important.

If missed, attackers might try:

  • Username: admin
  • Password: admin

and gain admin access.

Advanced Insight

Security Misconfiguration is NOT a bug. It’s a discipline problem.

Most developers:

  • Focus on features
  • Ignore deployment & configurations

Real engineers:

  • Secure systems at both infrastructure and application levels

Cloudflare Workers Has a Free Edge Serverless Platform With 100K Daily Requests

Cloudflare Workers runs JavaScript at the edge in 300+ data centers. Your code executes within milliseconds of your users.

Free Tier

  • 100K requests/day — free forever
  • Workers KV — 100K reads, 1K writes/day
  • R2 storage — 10GB free, zero egress fees
  • D1 database — 5M rows read, 100K writes/day
  • Durable Objects — stateful edge computing

Hello World

export default {
  async fetch(request) {
    return new Response('Hello from the edge!');
  },
};

Deploy

npm create cloudflare@latest
npx wrangler deploy

Workers vs Lambda

Feature Workers Lambda
Cold start 0ms 100-1000ms
Locations 300+ ~30 regions
Free tier 100K/day 1M/month

Need edge computing help? Check my work on GitHub or email [email protected] for consulting.

ChatGPT Prompt Engineering for Freelancers: Unlocking the Power of AI for Business Growth

ChatGPT Prompt Engineering for Freelancers: Unlocking the Power of AI for Business Growth

As a freelancer, you’re constantly looking for ways to streamline your workflow, improve efficiency, and deliver high-quality results to your clients. One tool that has been gaining traction in recent months is ChatGPT, a powerful AI model that can assist with a wide range of tasks, from content generation to code completion. However, to get the most out of ChatGPT, you need to master the art of prompt engineering. In this article, we’ll explore the world of ChatGPT prompt engineering, provide practical steps and code examples, and discuss how you can monetize your new skills.

What is ChatGPT Prompt Engineering?

ChatGPT prompt engineering is the process of crafting high-quality input prompts that elicit specific, accurate, and relevant responses from the ChatGPT model. The goal is to provide the model with enough context, constraints, and guidance to generate output that meets your needs and exceeds your expectations. By mastering prompt engineering, you can unlock the full potential of ChatGPT and use it to automate tasks, generate content, and even create new business opportunities.

Step 1: Define Your Objective

Before you start crafting prompts, you need to define your objective. What do you want to achieve with ChatGPT? Are you looking to generate content, complete code, or provide customer support? Be specific and clear about your goals, as this will help you design more effective prompts. For example, let’s say you want to use ChatGPT to generate a Python function that calculates the area of a rectangle. Your objective would be to create a prompt that elicits a response with a correct, well-documented, and readable code snippet.

Step 2: Choose the Right Prompt Structure

The structure of your prompt can significantly impact the quality of the response. ChatGPT supports various prompt structures, including:

  • Zero-shot prompts: These prompts provide minimal context and rely on the model’s prior knowledge to generate a response.
  • Few-shot prompts: These prompts provide a few examples or context to help the model understand the task and generate a response.
  • Chain-of-thought prompts: These prompts provide a series of steps or reasoning to help the model generate a response.

For example, if you want to generate a Python function that calculates the area of a rectangle, you could use a few-shot prompt like this:

# Define a function that calculates the area of a rectangle
# The function should take two arguments: length and width
# The function should return the area of the rectangle

# Example:
# area_of_rectangle(5, 3) should return 15

# Your function here:

This prompt provides context, constraints, and guidance to help the model generate a correct and readable code snippet.

Step 3: Refine Your Prompt

Once you’ve crafted your initial prompt, refine it by adding more context, constraints, or guidance. You can use various techniques, such as:

  • Providing more examples: Add more examples to help the model understand the task and generate a response.
  • Specifying output format: Specify the output format, such as a Python function or a JSON object.
  • Adding constraints: Add constraints, such as a specific library or framework, to help the model generate a response that meets your needs.

For example, if you want to generate a Python function that calculates the area of a rectangle using the NumPy library, you could refine your prompt like this:

# Define a function that calculates the area of a rectangle using NumPy
# The function should take two arguments: length and width
# The function should return the area of the rectangle as a NumPy array

# Example:
# area_of_rectangle(5, 3) should return np.array([15])

# Your function here:

This refined prompt provides more