Simulating P2P Attacks and Teaching AI to Resume Sessions

TL;DR

It’s been one of those weeks where the terminal never really closed. I clocked 78 commits and pushed 4 PRs across 9 different repositories, maintaining a perfect 7-day streak. The bulk of the heavy lifting happened in the P2P space—specifically simulating network attacks—and adding some much-needed session persistence to my AI coding tool, nanocoder. With over 21,000 lines added and about 6,500 deleted, it was a high-output week focused on building out complex systems and then immediately refining them.

WHAT I BUILT

The star of the show this week was definitely P2P-Attack-Simulation. I’ve been obsessed with how decentralized networks handle adversarial conditions, and I finally merged a massive PR for Topology ts simulation. We’re talking 18,756 additions and 6,436 deletions. I essentially rewrote how the network topology is represented in TypeScript to make it more modular. Before, the simulation was a bit too rigid; now, I can spin up different node behaviors and see how the gossip protocols hold up when half the network starts acting malicious. It’s one thing to read about Sybil attacks in a paper, but seeing the message propagation latency spike in your own simulation is a different kind of satisfying.

Over in nanocoder, I spent a good chunk of time on the developer experience. If you’ve used AI coding assistants, you know the pain of losing context or having to restart a session from scratch. I implemented a new Feature /resume command to continue session. This involved a fair bit of state management (about 2,300 lines of code) to ensure that when you come back to a project, the AI knows exactly where you left off. It’s all about reducing friction. I want to spend my time coding, not re-explaining my file structure to a LLM.

I also dipped back into the low-level world with the networking repo. This is where I get my Rust fix. I merged two PRs here: one to Implement proper RST generation and another to refactor: remove timer module and associated functionality. The RST (Reset) generation is crucial for proper TCP handling—basically telling the other side “I have no idea what you’re talking about, let’s start over.” The refactor felt great, too. I realized the custom timer module was overkill for what we needed, so I ripped out 137 lines of brittle code. Less code, fewer bugs.

Beyond the big features, I kept the wheels turning on several other projects. I pushed 10 commits to py-libp2p and 1 to dotnet-libp2p, mostly keeping the Python and C# implementations of the p2p stack in sync. I also spent some time tweaking my environment, with 8 commits to my nvim config (because a dev’s work is never done when it comes to Lua plugins) and 7 commits to my notes repo, which is where I dump my raw thoughts on C and systems programming.

PULL REQUESTS

I managed to get four significant PRs merged this week, and they really tell the story of my transition from “building the foundation” to “polishing the experience.”

The Topology ts simulation PR was the most exhausting but rewarding. It wasn’t just about adding lines; it was about restructuring the entire simulation engine. I had to ensure that the TypeScript types correctly reflected the state of a peer at any given millisecond during an attack.

On the AI side, the Feature /resume command in nanocoder was a fun challenge in serialization. Making sure the session state could be saved and reloaded without losing the “vibe” of the conversation took a few iterations, but it’s finally in a place where it feels seamless.

The Rust work in the networking repo was much more surgical. Implementing RST generation required me to get back into the weeds of packet headers, while the timer module refactor was a classic “addition by subtraction” move.

CODE REVIEWS

I only did one formal review this week, but it was a deep one. I looked at a PR for py-ipld-dag titled Add Block API, IPLD data model, codecs, registry, and tests.

I actually ended up requesting changes on this one. IPLD (InterPlanetary Linked Data) is the backbone of how we structure data in the p2p world, and the proposed implementation of the Block API felt a bit too coupled to specific codecs. I suggested a more registry-based approach to make it easier for others to plug in new data formats later. It’s important to get these base-level APIs right early on, or you end up paying for it in technical debt for years.

TECH STACK

This week was a true polyglot marathon.

TypeScript was my primary driver for the simulation work and the AI tooling. With over 15 million bytes of TS in my environment, it’s definitely my “get things done” language. But Python isn’t far behind—especially with the work on py-libp2p and the IPLD reviews.

The Rust work in the networking repo reminded me why I love the language: the compiler is a jerk until you get it right, and then your code just works. I also touched some Lua for my Neovim setup and a bit of C for my personal notes and experiments.

My add/delete ratio was pretty high (21k vs 6k), which usually means I’m in a heavy feature-building phase. However, that 6k deletions in the P2P repo shows I’m not just piling code on top of code—I’m actively replacing the old stuff as I go.

And yeah, that 7-day streak feels good. It wasn’t a forced grind; I just genuinely had something I wanted to solve every single morning when I woke up.

WHAT’S NEXT

Next week, I’m planning to take the P2P simulation even further. Now that the topology is flexible, I want to start implementing specific Eclipse attack scenarios to see how quickly a node can be isolated from the rest of the network.

I’m also keeping an eye on the feedback for the /resume command in nanocoder. There are probably some edge cases with large file buffers that I’ll need to iron out.

On the Rust side, now that RST generation is handled, I might start looking into better congestion control mechanisms for the networking library. There’s always more to optimize. See you in the commit logs!

Cómo enviar Emails desde Oracle APEX usando OCI Email Delivery

Menu Email Domain

📑 Tabla de contenidos

  • 📦Introducción
    • Dos opciones para enviar Emails desde OCI
    • OCI Email Delivery
  • ⚙️Configuración paso a paso
    • 1. Crear Email Domain
    • 2. Crear claves DKIM
    • 3. Configurar el registro DKIM en tu proveedor de DNS.
    • 4. Verificar el estado de claves DKIM
    • 5. Configurar SPF
    • 6. Generar credenciales SMTP en OCI
    • 7. Crear remitente aprobado (Approved Sender) en OCI
    • 8. Configurar Servicio SMTP en Autonomous Database
    • 9. Validar configuración de Email Delivery
    • 10. Prueba de envío de Email desde Apex
    • 11. Gestión de Emails
  • 🚀Próximo paso

¿Cómo se pueden enviar Emails desde OCI?

En Oracle Cloud Infrastructure (OCI) existen dos formas principales de configurar SMTP para enviar emails desde tus aplicaciones:

  1. Usar el servicio nativo de OCI: Email Delivery (SMTP)
  2. Usar un proveedor SMTP de terceros

En este tutorial vamos a configurar OCI Email Delivery para enviar emails desde Oracle APEX ejecutándose en Autonomous Database.

Sin embargo, es importante saber que no estás obligado a usar SMTP de OCI. También puedes usar proveedores externos como: SendGrid, Mailgun, Amazon SES, Brevo, etc. En este caso, simplemente configuras en tu aplicación: SMTP Host del proveedor, Puerto SMTP y Usuario/contraseña o API Key.
OCI en este escenario solo actúa como la infraestructura donde corre tu aplicación, mientras que el envío de emails lo gestiona el proveedor externo.

OCI Email Delivery: SMTP nativo de Oracle Cloud

Oracle Cloud Infrastructure (OCI) tiene su propio servicio para envío de correos llamado Email Delivery.

Este servicio forma parte de OCI y permite enviar emails desde tus aplicaciones usando SMTP, sin depender de proveedores externos.

OCI Email Delivery proporciona:

  • Un servidor SMTP administrado por Oracle
  • Credenciales SMTP generadas en IAM (Identity and Access Management)
  • Integración directa con recursos dentro de tu VCN (Virtual Cloud Network)

Esto permite que aplicaciones que corren dentro de OCI — como Oracle APEX en Autonomous Database — puedan enviar emails de forma segura y confiable.

⚙️ Manos a la obra: Configuración paso a paso

1. Crear Email Domain

Después de iniciar sesión en OCI con una cuenta de administrador,
ir a 👉 Menu → Developer Services → Email Delivery → Email Domains
Seleccionar Create Email Domain
Menu Email Domain

Asegúrese de que el compartment seleccionado sea el correcto antes de continuar.
Create Email Domain
⚠️ Importante! Debes tener acceso para modificar los registros DNS del dominio que utilices.

Ingresa el nombre de Email Domain que utilizarás para enviar correos. En este caso es ‘midominio.com’.

Este debe ser un dominio propio o un dominio cuyo DNS puedas administrar, ya que durante la configuración deberás agregar registros DNS para verificar y autenticar el dominio (por ejemplo, registros SPF y DKIM).

El dominio que ingreses será el que utilizarás en la dirección “From” de los correos enviados desde tu aplicación. Por ejemplo: From: [email protected]

Dominio Active
Unos segundos después de crear el dominio, su estado cambiará a ‘Active’. Y DKIM Signing lo verás ‘Inactive’.

Cuando el dominio esté activo, podrás continuar con el siguiente paso: crear las claves DKIM. Estas claves permiten autenticar los correos enviados desde tu dominio. Para ello selecciona la solapa DKIM.

2. Crear claves DKIM

DKIM (DomainKeys Identified Mail) es un mecanismo de autenticación de correo que permite verificar la legitimidad de los emails enviados desde un dominio.

Con DKIM:

  • ☑️ Los correos se firman digitalmente al momento de enviarse.
  • ☑️ El servidor receptor puede verificar que el mensaje no fue modificado durante la transmisión.
  • ☑️ Se confirma que el dominio del remitente autorizó el envío del correo.

Esto ayuda a mejorar la confianza del correo y reduce la probabilidad de que los mensajes sean marcados como spam.

Primero haz clic en Add DKIM para crear una nueva clave DKIM para tu dominio.
Add DKIM

Luego selecciona Add New DKIM

Add New DKIM

¿Por qué elegir Add New DKIM?
Porque:

  • ☑️ OCI genera automáticamente la clave DKIM por ti.
  • ☑️ Te proporciona un registro CNAME que deberás agregar en el DNS de tu dominio.
  • ☑️ OCI firmará automáticamente los correos enviados desde ese dominio.

La opción Import Existing DKIM, se utiliza sólo si ya generaste previamente un par de claves DKIM en otro sistema. Es poco común en la mayoría de los casos.

Selecciona Add New DKIM y presiona Next

DKIM Selector

En esta pantalla debes ingresar un DKIM Selector.

El selector es simplemente un identificador que se utiliza para generar y organizar las claves DKIM de tu dominio.

OCI sugiere usar un patrón como:

<prefix>-<shortregioncode>-<yyyymm>

Este formato es solo una convención que ayuda a organizar futuras rotaciones de claves DKIM.
Por ejemplo, podrías usar algo como: oci-us-202603
En mi caso utilicé oci1 como identificador del selector.
Luego presiona Next

DKIM Record

Haz clic en Generate DKIM Record.

OCI generará automáticamente el registro CNAME, mostrando el Record y el Value correspondientes.

El CNAME generado es el que deberás copiar y agregar en el DNS de tu dominio para completar la verificación DKIM.

Presiona Add DKIM para terminar de crear la clave. Verás que Email Domain está activo, pero aún no DKIM Signing.

DKIM Inactive

Para activar las claves deberás ir al panel de control de tu DNS.
En mi caso, administro el DNS con DonWeb, por lo que a continuación te mostraré cómo agregar este registro desde su panel de control.

3. Configurar el registro DKIM en tu proveedor de DNS.

⚠️ Si tu dominio está en otro proveedor (Cloudflare, GoDaddy, Namecheap, etc.), el proceso es el mismo: solo debes crear un registro CNAME con los valores que te proporcionó OCI.

Ahora debes agregar el registro CNAME generado por OCI en el DNS de tu dominio.

🔹 Ejemplo con DonWeb

En mi caso, administro el dominio con DonWeb. Los pasos son:

  1. Iniciar sesión en el panel de DonWeb.
  2. Seleccionar el dominio que deseas configurar.
  3. Hacer clic en Gestionar.
  4. Ir a la opción Nameservers y Zona DNS.

Una vez allí, verás una pantalla como esta donde podrás administrar los registros DNS de tu dominio.

Panel DNS DonWeb

Luego, haz clic en Agregar Registro, e ingresa los valores proporcionados por OCI. En mi caso:

[Tipo] = CNAME
CNAME Record [Nombre] = oci1_domainKey.midominio.com
CNAME Value [Contenido]= oci1_midominio.com.dkim.gur1.oraclemaildelivery.com

Add Record CNAME

Presiona Guardar
En la siguiente pantalla podrás ver el registro agregado en tu zona DNS.

Registros DNS

⚠️ Importante: Puedes tener varios registros DKIM configurados en tu dominio al mismo tiempo. Esto es completamente válido, ya que cada proveedor SMTP utiliza un selector distinto para firmar los correos, permitiendo que múltiples servicios envíen emails desde el mismo dominio sin conflictos.

En este ejemplo se observa el registro mail._domainkey.midominio.com, que corresponde a otro DKIM configurado para un servicio distinto.

Por este motivo, cada servicio de correo utiliza: su propia clave DKIM, su propio selector y su propio registro DNS.

⏳Después de agregar el registro, debes esperar a que la configuración DNS se propague. Este proceso no es inmediato y puede demorar algunos minutos (o más, dependiendo del proveedor de DNS). Y una vez propagado, OCI podrá verificar automáticamente el registro DKIM.

4. Verificar el estado DKIM

Una vez que el registro DNS se haya propagado, vuelve a la consola de OCI para verificar el estado de DKIM.

Ir a 👉 Menu → Developer Services → Email Delivery → Email Domains y selecciona tu dominio.

En la solapa DKIM, haz clic en Refresh DKIM Status.

Refresh Status DKIM

Si todo está configurado correctamente, el campo DKIM Signing Status debería aparecer como Active.

Estados Active

Si todos los estados aparecen como Active, significa que la configuración se realizó correctamente.

  • ☑️ OCI puede firmar los correos desde tu dominio (en mi caso midominio.com)
  • ☑️ DKIM está activo y validado
  • ☑️ La configuración DNS es correcta

5. Configurar SPF

SPF (Sender Policy Framework) es un mecanismo de autenticación de correo que permite definir qué servidores están autorizados a enviar emails desde tu dominio.
Esto ayuda a evitar suplantación de identidad (spoofing) y mejora la entregabilidad de tus emails.

Para configurar SPF en OCI Email Delivery debes agregar un registro TXT en el DNS de tu dominio.

En mi caso vuelvo al panel de administracion de DNS de mi dominio en DonWeb, si no existe un registro creas uno nuevo con los siguientes valores:

[Tipo] = TXT
TXT Name [Nombre] = midominio.com
TXT Value [Contenido]= v=spf1 include:spf.oraclecloud.com -all

¿Qué significa este registro?

  • v=spf1 → versión de SPF
  • include:spf.oraclecloud.com → autoriza a OCI a enviar correos desde tu dominio
  • -all → indica que otros servidores no autorizados serán marcados como sospechosos

⚠️ Importante: Si ya tienes un registro SPF configurado, no debes crear otro. En su lugar, debes incluir tu valor de OCI SPF al registro existente.

En mi caso ya tengo un SPF configurado por lo que modifico el registro agregando a continuacion a spf.oraclecloud.com

Registro SPF

6. Generar credenciales SMTP en OCI

Las credenciales SMTP son las que permitirán a tu aplicación (en nuestro caso una app en Oracle APEX) autenticarse contra el servidor de OCI Email Delivery para enviar correos.
No es necesario crear un usuario completamente nuevo, pero es recomendable por buenas prácticas. Ya que usarlo exclusivamente para el envío de correos facilita la administración y auditoría y mejora la seguridad.

Regresa a la consola de OCI y, desde el menú principal, ve a 👉 Identity & Security → Domains.

Selecciona el dominio actual (Current Domain), verifica que estás en el compartment correcto.

Luego, dirígete a User Management, selecciona el usuario con el que enviarás los correos y accede a la solapa SMTP Credentials.

Desde allí, podrás generar las credenciales haciendo clic en Generate Credentials.

Generate Credentials

Luego de ingresar un nombre para las credenciales, en mi caso APEX OCI EMAIL, podrás ver el Username y Password generados.

⚠️ Importante! Solo verás el password al generar las credenciales por lo que debes guardarlo. No podrás recuperarlo posteriormente.

Credentials data

Luego haz clic en Close, verás un listado de todas las credenciales generadas para tu usuario.

Credential list

7. Crear un remitente aprobado (Approved Sender) en OCI

⚠️ Solo podrás enviar correos desde direcciones que estén registradas como Approved Senders.

Se debe configurar un remitente aprobado para todas las direcciones “From:” que envíen correos electrónicos a través de OCI; de lo contrario, el correo será rechazado.

Ir a 👉 Menu → Developer Services → Email Delivery → Approved Senders

Menu Approved Senders

Haz click en Create Approved Sender

Create Approved Sender

Ingresa la dirección de correo que utilizarás como remitente (From:). En mi caso [email protected].
Esta dirección debe pertenecer al mismo dominio que configuraste y verificaste con DKIM.

⚠️ Importante: El dominio del remitente debe tener el mismo dominio que configuraste y verificaste con DKIM. De lo contrario, los correos pueden ser marcados como spam o incluso rechazados por los servidores de destino.

Listado de Approved Senders

8. Configurar Servicio SMTP en Autonomous Database

Antes de comenzar la configuración en Oracle APEX, debemos obtener los datos SMTP necesarios desde OCI Email Delivery que utilizaremos en los siguientes pasos.

Para obtener estos datos, navega a:
👉 Menu → Developer Services → Email Delivery → Configuration

SMTP DATA

En esta pantalla encontrarás la información necesaria para la conexión SMTP. Copia los siguientes datos:

  • SMTP Public Endpoint
  • SMTP Port (generalmente 587)

Con estos datos y las credenciales generadas en el paso 6, podrás configurar los parámetros SMTP en tu instancia de Oracle APEX.

Accede a tu Autonomous Database con un usuario que tenga permisos de administrador.

Puedes hacerlo utilizando cualquiera de las siguientes herramientas: Oracle APEX, SQL Workshop, SQL Developer, etc.

En mi caso, me conecto directamente desde OCI a través de:
👉Database Actions → SQL

Open SQL User Admin

Ejecuta el siguiente bloque PL/SQL para configurar los parámetros SMTP en tu instancia de Oracle APEX:

BEGIN
    -- Paso 8: Datos SMTP Host (public endpoint de OCI)
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_HOST_ADDRESS', 'smtp.public_endpoint');

    -- Paso 8: Datos SMTP Puerto 
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_HOST_PORT', '587');

    -- Paso 6: Credenciales SMTP Usuario
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_USERNAME', 'SMTPcredentials.username');

    -- Paso 6: Credenciales SMTP Password
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_PASSWORD', 'SMTPcredentials.password');

    -- Paso 7: Direccion de envio (From) configurado en Approved Sender
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_FROM', '[email protected]');

   COMMIT;
END;
/

SQL SET_PARAMETER

9. Validar configuración de Email Delivery

Antes de enviar correos desde Oracle APEX, debemos asegurarnos de que el proveedor SMTP esté correctamente configurado y disponible para su uso con APEX_MAIL.

Ejecuta el siguiente código desde tu cliente SQL, asegurándote de utilizar un usuario con permisos de administrador:

BEGIN
  APEX_INSTANCE_ADMIN.VALIDATE_EMAIL_CONFIG;
END;
/

VALIDATE_EMAIL_CONFIG

Si se informa algún error (por ejemplo, "ORA-29279: SMTP permanent error: 535 Authentication credentials invalid"), ajuste los parámetros SMTP o revise la configuración y repita la validación.

10. Prueba de envío de Email desde APEX

Para enviar un email de prueba, inicia sesión en Oracle APEX y desde 👉 SQL Workshop → SQL Commands, ejecuta lo siguiente:

BEGIN
  APEX_MAIL.SEND(
    p_from => '[email protected]', -- Ex: [email protected]
    p_to   => '[email protected]',
    p_subj => 'Email from Oracle Autonomous Database',
    p_body => '<table width="100%" cellpadding="0" cellspacing="0" bgcolor="#ff0000"> <tr> <td align="center" style="padding:40px; font-family:''Trebuchet MS'', Arial, sans-serif; color:#000000; font-size:20px;"> <b>MAIL ENVIADO DESDE ORACLE APEX</b> <br><small>Configurado con OCI Email Delivery</small> </td> </tr> </table>');
  APEX_MAIL.PUSH_QUEUE;
END;
/

APEX_MAIL.PUSH_QUEUE se utiliza para enviar inmediatamente los correos que están en cola en Oracle APEX.

APEX_MAIL.SEND

Cuando usas APEX_MAIL.SEND, el correo no se envía al instante, sino que queda en una cola interna.

Al ejecutar APEX_MAIL.PUSH_QUEUE, APEX procesa esa cola y realiza el envío.

💡 Nota:

En entornos productivos, el envío suele ejecutarse automáticamente mediante un job programado, en lugar de forzarlo manualmente con APEX_MAIL.PUSH_QUEUE.
Forzar el envío inmediato de todos los correos puede impactar la performance del sistema.
Por este motivo, es recomendable definir una estrategia de envío adecuada según el entorno: Desarrollo, Testing, Producción.

Email recibido usando APEX_MAIL.SEND:

MAIL FROM APEX

11. Gestión de emails enviados y con errores

Dentro de la consola de administración de Oracle APEX, navega a 👉 Manage Instance → Mail Queue

Acá podrás ver los errores de los mails que no pudieron ser enviados. Ejemplo: ORA-29279: SMTP permanent error: 535 Authorization failed: Envelope From address <info@dominio_sin_autorizacion.com> not authorized, lo cual ocurre cuando se envía un mail con una dirección de email no autorizada en el From: (una de las causas puede ser que ese correo no haya sido agregado en Approved Senders ).

Gestion Mail Queue

También se puede supervisar la entrega de correos, consultando las tablas APEX_MAIL_QUEUE y APEX_MAIL_LOG desde SQL con usuario administrador.

select * from APEX_MAIL_QUEUE

Table APEX_MAIL_QUEUE

select * from APEX_MAIL_LOG

Table APEX_MAIL_LOG

🚀Próximo paso

Ahora ya puedes enviar correos desde APEX. En el siguiente post veremos cómo utilizar Email Templates en Oracle APEX, para crear mensajes más profesionales y reutilizables.

⬆️ Ir a Tabla de Contenidos

How to compare team capacity before committing to a software deadline

Calculate team options - Motionode

What most people expect

Change the team → change the timeline.

More devs → faster delivery.

What actually happens

In many cases, the timeline barely changes.

But:
Same duration. Very different execution risk.

Why?

Because:

  • The total work stays the same
  • The steps are still sequential (design → build → test → deploy)
  • Someone still has to do each step

So instead of reducing time, you just redistribute it.

What really changes

Different team setups change how your project behaves:

  • idle time between steps
  • how busy each person is
  • how much coordination is needed
  • where bottlenecks appear

Not necessarily the final date.

What this looks like in practice

Calculate team options - Motionode

You can run 3 different team setups and still get ~4 weeks.

But:

Simulation results - Motionode

  • one has overloaded devs
  • one has idle time
  • one has smoother flow

This is why many projects don’t fail because of bad estimates. They fail because the execution model was never stress-tested.

If your planning process only outputs a date,
you’re missing the part that actually determines whether you hit it.

15 Java String Method Questions I Practiced — Here’s What I Actually Learned

15 Java String Method Questions I Practiced — Here’s What I Actually Learned

I’ve been learning Java for a while now, and strings are everywhere. But knowing that String exists and actually knowing how to use it in real situations are two very different things.

Here’s everything I picked up, question by question.

Q1 — Why Does Login Fail Even When the Password is Correct?

Scenario: User types Admin123 (with a trailing space). Login fails even though the password matches.

This one is so common in real applications and nobody talks about it enough. The fix is .trim() — it removes all leading and trailing whitespace from a string before you compare it.

if (password.equals(CheckPassword.trim())) {
    System.out.println("Login Success");
}

What I learned: Never compare user input directly. Always .trim() first. Users accidentally add spaces all the time — especially on mobile keyboards.

Q2 — UI Sends “Vijay” but Database Has “vijay” — How to Compare Safely?

Scenario: The UI sends the name with a capital V but your database stores it lowercase.

.equals() is case-sensitive — “Vijay” and “vijay” would fail. The fix is .equalsIgnoreCase() which ignores the case completely.

if (DataBaseName.equalsIgnoreCase(UIName.trim()))
    System.out.println("login success");

What I learned: Always use .equalsIgnoreCase() for usernames and names. Use .equals() only when case matters — like passwords.

Q3 — What’s the Difference Between == and .equals()?

This question genuinely confused me before I practiced it.

String s1 = "JAVA";
String s2 = new String("JAVA");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true
  • == compares memory addresses (reference). s1 is in the String Pool, s2 is a new object in the heap — different address, so false.
  • .equals() compares the actual value — both contain “JAVA”, so true.

What I learned: Never use == to compare strings in Java. Always use .equals(). The == trap has broken countless real applications.

Q4 — Validate an Email Address

Scenario: Keep asking the user for an email until it contains @ and .

if (CheckEmail.contains("@") && CheckEmail.contains(".")) {
    System.out.println("Your Email : " + CheckEmail);
    condition = false;
}

.contains() checks if a string has a specific sequence of characters inside it. I put it inside a while loop to keep asking until the input is valid.

What I learned: .contains() is great for quick format checks. For production-level validation you’d use regex, but .contains() is perfect for learning the logic first.

Q5 — Convert a String to an Integer

Scenario: User types “1234” as text. You need it as a number to do math.

int number = Integer.parseInt(scan.nextLine());

Integer.parseInt() converts a numeric string into an actual int. If the user types letters instead of numbers, it throws a NumberFormatException — which is why real apps wrap this in try-catch.

What I learned: Keyboard input is always a String. Whenever you need a number from the user, Integer.parseInt() is your go-to.

Q6 — Remove Extra Spaces from Start and End

String extraSpace = " welcome to java ";
System.out.println(extraSpace.trim()); // "welcome to java"

.trim() again — but this time I saw it as a standalone method, not just inside a comparison. It’s great for cleaning up any user-facing text before displaying or storing it.

Q7 — Count How Many Times a Word Appears

Scenario: "Java is easy, Java is powerful" — how many times does “Java” appear?

This one I still had a doubt on during practice. The clean way to count occurrences:

String count = "java is easy , java is powerful";
int occurrences = count.split("java", -1).length - 1;
System.out.println(occurrences); // 2

Split the string by the word you’re looking for. The number of pieces minus 1 = the number of occurrences. The -1 flag makes sure empty trailing strings are counted too.

What I learned: There’s no .countOccurrences() method in Java — you build it using .split() or a loop.

Q8 — Reverse a String Without StringBuilder

Scenario: Reverse "abishek" without using StringBuilder.reverse().

String word = "abishek";
String reverse = "";

for (int i = 0; i < word.length(); i++) {
    reverse = word.charAt(i) + reverse;
}
System.out.println(reverse); // kehsiba

.charAt(i) picks out one character at a time by its index. Each character is added to the front of the result string, which builds the reverse naturally.

What I learned: .charAt() is how you access individual characters in a string. This loop pattern is a classic interview question — worth memorizing.

Q9 — Check if a String is a Palindrome

A palindrome reads the same forwards and backwards — like “madam” or “racecar”.

String reverse = "";
for (int i = 0; i < word.length(); i++) {
    reverse = word.charAt(i) + reverse;
}
System.out.println(reverse.equals(word) ? "it is palindrome" : "not palindrome");

Reverse the string using the same loop as Q8, then compare it to the original with .equals(). I also used the ternary operator here which keeps the code clean.

What I learned: Palindrome check = reverse + compare. That’s it.

Q10 — Remove Duplicate Characters

Scenario: Input "aabbccddee" → Output "abcde"

String duplicate = "aabbccddee";
String result = "";

for (int i = 0; i < duplicate.length(); i++) {
    if (result.indexOf(duplicate.charAt(i)) == -1) {
        result += duplicate.charAt(i);
    }
}
System.out.println(result); // abcde

.indexOf() returns the position of a character in a string — or -1 if it’s not there. I use that -1 check to decide if the character has already been added to the result.

What I learned: .indexOf() returning -1 means “not found” — this is a very useful pattern for checking membership in a string.

Q11 — Extract Part of a String (Log Parser)

Scenario: Log entry is "ERROR: File not found". You only want "File not found".

String logFormat = "ERROR: File not found";
System.out.println(logFormat.substring(7)); // File not found

.substring(7) starts from index 7 and returns everything after it. You can also do .substring(start, end) to get a specific range.

What I learned: .substring() is essential for parsing structured text like logs, file paths, or formatted data where you know the position of what you need.

Q12 — Parse a CSV String

Scenario: "Abishek,Chennai,23" — extract name, city, age separately.

String CSV = "Abishek,Chennai,23";
String[] result = CSV.split(",");

System.out.println("Name: " + result[0]);
System.out.println("City: " + result[1]);
System.out.println("Age:  " + result[2]);

.split(",") breaks the string wherever it finds a comma and returns an array of pieces. I then access each piece by its array index.

What I learned: .split() is how you parse any delimiter-separated data — CSV files, API responses, config strings, all of it.

Q13 — Replace Words in a String

Scenario: Replace “Java” with “Spring Boot” and “Love” with “Hate” in one sentence.

String given = "I Love Java Programming";
String result = given.replace("Java", "C++").replace("Love", "Hate");
System.out.println(result); // I Hate C++ Programming

.replace() swaps every occurrence of a word with something new. You can chain multiple .replace() calls together since each one returns a new String.

What I learned: String methods in Java return new strings — they don’t modify the original. This is because strings in Java are immutable. You have to store the result.

Q14 — Validate a Mobile Number

Scenario: Validate that a number is 10 digits and starts with 6, 7, 8, or 9.

if (number.matches("[6-9][0-9]{9}")) {
    System.out.println("Valid number");
}

.matches() checks if the entire string matches a regular expression (regex) pattern. [6-9] means any digit from 6 to 9. [0-9]{9} means exactly 9 more digits after that. Together = a valid Indian mobile number format.

What I learned: .matches() with regex is the professional way to validate formats — phone numbers, emails, passwords, PINs. This was my first time really using regex and it clicked here.

Q15 — Split a String by a Custom Delimiter

Scenario: "apple#banana#mango" — split by # and store in an array.

String GivenString = "apple#banana#mango";
String[] arr = GivenString.split("#");
System.out.println(Arrays.toString(arr)); // [apple, banana, mango]

Same concept as Q12 but with a custom delimiter. Arrays.toString() prints the array in a readable format instead of the raw memory address.

What I learned: .split() works with any delimiter — comma, hash, pipe, space. And always use Arrays.toString() when printing arrays or you’ll get gibberish output.

The Full Method Cheat Sheet

Here’s every method I used across all 15 questions:

Method What It Does
.trim() Removes whitespace from start and end
.equals() Compares values — case sensitive
.equalsIgnoreCase() Compares values — ignores case
.contains() Checks if a substring exists inside
.charAt(i) Gets the character at a specific index
.length() Returns total number of characters
.indexOf() Finds position of a character (-1 if not found)
.substring(n) Returns string from index n onwards
.substring(a, b) Returns string from index a to b
.split(delimiter) Breaks string into array by a separator
.replace(old, new) Swaps all occurrences of a word
.matches(regex) Checks if string matches a pattern
Integer.parseInt() Converts numeric string to int

What I Actually Took Away From This

The biggest thing these 15 questions taught me is that String methods are not just syntax — they’re tools for solving real problems.

Before this, I knew .trim() existed. Now I know when I need it — every time I take user input. I knew .split() was a thing. Now I know it’s how you parse CSV data. I knew == and .equals() were different. Now I know exactly why — and why getting it wrong breaks login systems.

If you’re learning Java, I’d genuinely recommend doing scenario-based practice over reading method documentation. The methods stick when you see the problem they solve.

GCC vs Clang: Same Instructions, Different Performance (AGU Insight)

GCC VS CLANG

*I noticed something interesting while running a GCC vs Clang benchmark.
*

Same code. Same machine.
Both loops are scalar (no vectorization).

Yet… GCC consistently used fewer CPU cycles.

At first, this doesn’t make sense.

If both:

execute roughly the same instructions
are not vectorised

Why is there a performance gap?

🔍 The Missing Piece: It’s Not Just Instructions
Most people focus on:
instruction count
vectorization

But in this case, that’s not the full story.

What actually matters more is:

  • how address computations are structured
  • how instructions are scheduled
  • how well latency is hidden

Here is the data

GCC VS CLANG

⚙️ AGU Pressure (Address Generation Units)

On x86 CPUs, memory instructions rely on AGUs (Address Generation Units).

Complex addressing patterns like:

base + index * scale + offset

👉 increase AGU pressure

Whereas simpler patterns like:
pointer++
👉 are cheaper and easier for the CPU to execute efficiently

🧪 What I Observed
GCC:
Generates simpler addressing patterns
Reduces AGU contention
Keeps execution more consistent
Clang:
Shows higher AGU pressure
More stalls
Less efficient scheduling (in this case)

⚡ Key Takeaway
It’s not just about what instructions exist.

It’s about:
How efficiently the compiler feeds the CPU pipeline

Same instruction count ≠ same performance.

📊 Why This Matters

In tight loops:

AGU pressure
addressing patterns
instruction scheduling

👉 can matter as much as (or more than) vectorization

🔗 Want to Dive Deeper?

👉 Full benchmark + assembly breakdown:

👉 Complete analysis article:

💬 Discussion

Have you seen cases where:

similar assembly
same instruction count

👉 still results in very different performance?

Would love to hear your observations.

S&OP Engineering V: The Autonomous Brain (Agentic AI)

1. The Execution Chasm

Companies invest millions in planning software and ERPs. They spend months integrating data, cleaning records, and tuning demand forecasts. Yet, at the end of the day, execution often relies on an analyst staring at a dashboard. A human reading charts, finally drafting an email to the plant director to request production adjustments.

That human delay is inefficient, error-prone, and, frankly, unnecessary in the modern era. Pure mathematics has no value if it isn’t executed.

The solution isn’t a rigid automation script, nor is it a chatbot you ask questions to. True modernization requires Agentic AI. We are not talking about ChatGPT. We are talking about Autonomous Agents: software entities with a specific role, a clear goal, and integrated with Tools that allow them to interact with real-world databases and APIs.

2. The Architecture: CrewAI + Supabase

For this Autonomous Brain, we have implemented a multi-agent architecture using CrewAI. Our digital team (“The Agency”) is composed of two key agents:

  • Senior Supply Chain Analyst: Its job is purely analytical. We don’t give it a PDF to summarize; we equipped it with a Python @tool that allows it to execute SQL queries against our Supabase database. It extracts the optimal production plan generated by PuLP in Chapter 4, identifies bottlenecks, and pinpoints the months where production stress is most critical.
  • Procurement & Plant Manager: Acts as the executor. Based strictly on the mathematical conclusions drawn by the Analyst, it drafts the definitive executive orders.

Crucial fact: They do not hallucinate. Their decisions are anchored in hard data mathematically retrieved from the central database.

3. The Code: Tools over Prompts

The true power of Agentic AI lies in providing LLMs with the ability to interact with external systems. The C-Suite must understand that we aren’t handing an Excel sheet to a language model; we are embedding our code with the ability to “think” and execute actions using the ReAct (Reasoning and Acting) pattern.

Here is a snippet of how we connect our Agent to Supabase:

from crewai.tools import tool
from supabase import create_client

@tool("fetch_latest_supply_plan")
def fetch_latest_supply_plan() -> str:
    """
    Connects to the Supabase database, finds the most recent execution of the 
    optimized production plan, and extracts the strategic plan for the 
    next 3 months across all SKUs.
    """
    supabase = create_client(SUPABASE_URL, SUPABASE_KEY)

    # 1. Find the most recent execution
    latest = supabase.table("supply_plans").select("execution_date").order("execution_date", desc=True).limit(1).execute()
    exec_date = latest.data[0]["execution_date"]

    # 2. Extract the optimal plan generated by PuLP
    response = supabase.table("supply_plans").select("*").eq("execution_date", exec_date).execute()

    # ... Format into structured Markdown to prevent hallucinations ...
    return report

# Instantiate the Analyst Agent by connecting it to the tool
analyst_agent = Agent(
    role='Senior Supply Chain Analyst',
    goal='Analyze the master production plan to detect bottlenecks and inventory risks.',
    backstory='You are a relentless analyst seeking capital efficiency. You hate excess inventory but fear stockouts...',
    verbose=True,
    allow_delegation=False,
    tools=[fetch_latest_supply_plan], # <-- We inject Database access here
    llm=gemini_llm
)

By compiling the agents into a Process.sequential pipeline, we orchestrate a workflow where the Analyst’s output directly fuels the actions of the Plant Manager.

4. The Money Shot

If we execute our “Crew”, this is exactly what the model returns in the terminal after querying the database and reasoning around the factory’s 12,000 monthly unit capacity limit:

[!NOTE]
SUBJECT: URGENT – July-September Production Plan | Bottleneck Alert

Hi Carlos,

After running the mathematical optimization model for the upcoming quarter, I confirm the Master Production Plan. We are facing a critical capacity bottleneck (12,000 units/month) during the summer that requires strict execution.

July (100% Capacity – Critical Risk):
The entire plant capacity must be exclusively dedicated to SKU-001 (12,000 units). Halt the SKU-002 and SKU-003 lines this month. The model has pulled forward this production to prevent stockouts in the following months.

August (100% Capacity – Production Mix):
The factory will remain at its limit. The distribution will be:

  • SKU-001: 5,000 units
  • SKU-002: 15 units
  • SKU-003: 6,985 units (High priority for the seasonal campaign)

September (83% Capacity – Stabilization):
The pressure will decrease. We will manufacture 8,000 units of SKU-001, 10 units of SKU-002, and 2,000 units of SKU-003, ensuring a buffer for line maintenance.

Please coordinate with purchasing to secure the raw materials for SKU-001 immediately for the first week of July. The algorithm leaves no margin for error regarding the 12,000 units for that month.

Regards,
Agentic S&OP Copilot (On behalf of Operations Management)

This email isn’t a generic template. It is an operational, justified, and unbiased decision, generated autonomously from a linear programming model and delivered in seconds.

5. The Sandbox

Want to see how they reason in real-time? I have prepared an interactive notebook so you can test the system yourself.

👉 Open the Agentic S&OP Google Colab

For security reasons, we have simulated the database return in the public environment. But if you insert your own Gemini API Key (it’s free), you will be able to watch the agents’ step-by-step reasoning process in the logs before they issue the final order.

6. Series Conclusion: The CTO Pitch

We have reached the end of our “S&OP Engineering” journey. Over the course of five chapters, we have evolved enterprise planning from its grimiest foundations to the technological bleeding edge:

  1. Foundations: We cleaned and normalized years of unstructured historical data.
  2. Vision: We implemented iterative Artificial Intelligence engines to forecast future demand with high precision (Prophet).
  3. Logic: We introduced Decision Sciences and Linear Programming to balance inventory constraints (PuLP).
  4. Scale: We elevated the mathematical model to the Enterprise level, dealing with shared capacity constraints and parallelized MLOps.
  5. Execution: We gave the system an Autonomous Brain to bridge the gap between the Dashboard and the real world.

The CTO Pitch: If your company is still planning demand with moving averages in Excel, you are bleeding money. Capital efficiency, stockout reduction, and intelligent workflow automation are no longer the future; they are the operational standard demanded by today’s margins.

It’s time to retire static models. Let’s talk about how to modernize your Supply Chain from its roots all the way to autonomous execution.

Supply Chain Attacks on Developers: Lessons from LiteLLM and Trivy

Supply chain attacks on developers have escalated dramatically in early 2026. Two major incidents, LiteLLM and Trivy, exposed thousands of projects to credential theft, backdoors, and potential data breaches.

These attacks represent a fundamental shift in how threat actors target software development. Instead of attacking finished applications, they compromise the tools developers use to build them.

The LiteLLM PyPI Compromise

In March 2026, malicious versions of LiteLLM appeared on PyPI, affecting thousands of Python projects.

What Happened:

Versions 1.82.7 and 1.82.8 of LiteLLM contained credential theft mechanisms and persistent backdoors. The attack was sophisticated, using obfuscated code and delayed execution to evade detection.

Attack Mechanism:

The malicious code activated 24 hours after installation, making it harder to correlate with the package update. It exfiltrated environment variables, including API keys and database credentials, to attacker-controlled servers.

Impact:

Over 15,000 projects downloaded the compromised versions before discovery. Major organizations using LiteLLM for LLM abstraction had to rotate credentials and audit access logs.

Discovery:

Security researchers at FutureSearch.ai identified the compromise through behavioral analysis. The malware attempted network connections to unusual domains, triggering automated alerts FutureSearch.ai, 2026.

The Trivy GitHub Actions Compromise

Trivy, a popular container vulnerability scanner, was compromised through its GitHub Actions integration.

Attack Vector:

Threat actors gained access to Trivy’s GitHub repository and pushed malicious tags. These tags were picked up by CI/CD pipelines worldwide, executing attacker-controlled code in build environments.

Scope:

The compromise affected any project using Trivy GitHub Actions with floating version tags. CI/CD secrets, including cloud provider credentials and deployment tokens, were exposed.

Exploitation:

The malicious code ran with the permissions of the CI/CD pipeline. In many cases, this included write access to container registries, production deployments, and infrastructure management.

Response:

Socket.dev and other security firms coordinated disclosure. GitHub revoked compromised tokens and worked with maintainers to secure the repository Socket.dev, 2026.

How Supply Chain Attacks Work

Understanding attack vectors is essential for defense.

Typosquatting:

Attackers publish packages with names similar to popular libraries. Developers mistype imports and install malware. Examples include reqeusts instead of requests, djano instead of django.

Dependency Confusion:

Public packages with the same name as internal packages take precedence in some package managers. Attackers upload public versions with higher version numbers, tricking build systems.

Compromised Maintainer Accounts:

Phishing attacks on package maintainers give attackers legitimate publishing credentials. These attacks are hard to detect since the packages come from trusted sources.

Build System Compromise:

Attackers target the infrastructure that builds packages. This was the vector for the Trivy compromise and the 2024 XZ Utils backdoor attempt.

Malicious Updates:

Legitimate packages are compromised and used to distribute malware. The LiteLLM attack followed this pattern, with attackers gaining access to the PyPI publishing pipeline.

Protecting Your Projects

Defense requires multiple layers of security.

Pin Dependencies:

Use exact version numbers in requirements files. Avoid floating versions like package>=1.0. Pin to specific hashes where possible.

# requirements.txt with hashes
litellm==1.82.6 
    --hash=sha256:abc123...

Private Registries:

Mirror public packages to private registries you control. Scan packages before internal distribution. This adds a review layer between public repositories and your builds.

Automated Scanning:

Integrate security scanning into CI/CD pipelines. Tools like Snyk, Socket.dev, and GitHub Advanced Security detect known malicious packages and vulnerable dependencies.

Least Privilege:

Run CI/CD pipelines with minimal permissions. Separate build, test, and deployment credentials. Use short-lived tokens with limited scope.

Dependency Review:

Audit new dependencies before adding them. Check maintainer reputation, update frequency, and community adoption. Avoid abandoned or single-maintainer projects for critical functionality.

Industry Response and Best Practices

The security community has responded with new tools and standards.

Software Bills of Materials (SBOM):

SBOMs inventory all dependencies and their versions. They enable rapid vulnerability assessment when new threats emerge. Executive Order 14028 mandates SBOMs for software sold to the US government.

Sigstore and Artifact Signing:

Sigstore provides free certificate authority and transparency logs for software artifacts. Signed packages can be verified against tampering. Adoption is growing across major package registries.

Package Manager Improvements:

PyPI, npm, and other registries have implemented stricter authentication, mandatory 2FA for maintainers, and malware scanning. These measures reduce but do not eliminate risk.

Security Frameworks:

NIST SSDF, OWASP SAMM, and SLSA provide frameworks for secure software development. Following these standards reduces supply chain attack surface.

The Economic Impact

Supply chain attacks have measurable business consequences.

Remediation Costs:

The average supply chain breach costs $4.5 million to remediate. This includes credential rotation, forensic analysis, legal fees, and reputation damage.

Development Velocity:

Security reviews slow development. Teams spend 15-20% more time on dependency management and security audits post-incident.

Insurance:

Cyber insurance premiums have increased 50% for software companies. Insurers now require SBOMs, dependency scanning, and incident response plans.

Regulatory:

The SEC now requires public companies to disclose material cybersecurity incidents within four days. Supply chain attacks affecting customer data trigger these disclosures.

FAQ

What is a supply chain attack?

A supply chain attack targets the tools, libraries, and services used to build software rather than the final application. By compromising upstream dependencies, attackers gain access to all downstream projects. These attacks are difficult to detect since the malicious code comes from trusted sources CISA, 2026.

How can I check if my project is affected?

Audit your dependency files (requirements.txt, package.json, Cargo.toml) for compromised versions. Use pip list or npm list to see installed packages. Security tools like Snyk and GitHub Dependabot alert on known malicious versions automatically.

What should I do if I used a compromised package?

Immediately rotate all credentials that may have been exposed. Check logs for unauthorized access. Update to clean versions of the package. Audit your codebase for signs of compromise. Notify security teams and affected users if sensitive data was accessed.

Are private package registries safer?

Private registries add a layer of control but are not inherently safer. They must be maintained, scanned, and updated like public registries. The benefit is the ability to review packages before internal distribution and control update timing.

How do I prevent dependency confusion attacks?

Use scoped packages (e.g., @company/package) for internal libraries. Configure package managers to prioritize private registries. Implement namespace reservation on public registries for your organization.

What is the future of supply chain security?

Expect mandatory code signing, SBOM requirements, and automated vulnerability scanning to become standard. Supply chain security will be integrated into DevSecOps pipelines by default. Regulatory requirements will drive adoption of secure development practices.

Conclusion

The LiteLLM and Trivy compromises are warnings, not anomalies. Supply chain attacks are increasing in frequency and sophistication. Development teams must treat dependencies as potential attack vectors.

The solution is not to avoid third-party code. Modern software development depends on open source. The solution is to manage dependencies with security in mind.

Pin versions. Scan for vulnerabilities. Use private registries. Implement least privilege. Build SBOMs. Sign artifacts. These practices add friction but prevent catastrophe.

The attackers are targeting your tools. Secure them before they do.

Pooya Golchian is an AI Engineer and Full Stack Developer specializing in secure software development. Follow him on Twitter @pooyagolchian for more insights on cybersecurity and development.

Top 7 AI Agent Frameworks in 2026: A Developer’s Comparison Guide

Top 7 AI Agent Frameworks in 2026: A Developer’s Comparison Guide
March 24, 2026 · 12 min read · By Paxrel

    There are now at least seven production-grade AI agent frameworks competing for your stack. Each takes a fundamentally different approach to the same problem: how do you build autonomous systems that actually work?

    We run an [AI agents newsletter](https://buttondown.com/paxrel) that's itself powered by an autonomous agent pipeline. We've read the docs, tried the APIs, and tracked the ecosystem since these tools launched. Here's our honest take on each framework as of March 2026.

    ## The Quick Comparison




                Framework
                Best For
                Model Lock-in
                Learning Curve



            **LangGraph**Complex stateful workflowsNoneSteep
            **CrewAI**Team-based agent systemsNoneEasy
            **AutoGen**Multi-agent conversationsNoneMedium
            **OpenAI Agents SDK**Fast prototyping with GPTOpenAI onlyEasy
            **Claude Agent SDK**Secure, sandboxed agentsClaude onlyMedium
            **Google ADK**Multimodal + A2AGemini-firstMedium
            **Smolagents**Lightweight code agentsNoneEasy



    ## 1. LangGraph
    Production-ready Model-agnostic Stateful

    LangGraph is the heavyweight. Built on top of LangChain, it models your agent's workflow as a directed graph where nodes are actions and edges are transitions. Every step gets checkpointed, so if your agent crashes mid-task, it picks up exactly where it left off.

    ### Why developers pick it

        - **Built-in persistence:** State checkpointing means long-running agents survive failures
        - **Explicit control flow:** You define exactly how your agent moves between steps
        - **Human-in-the-loop:** Native support for approval gates and manual overrides
        - **Model-agnostic:** Swap between OpenAI, Claude, Gemini, or local models


    ### The trade-offs

        - Steep learning curve &mdash; you need to think in graphs, not scripts
        - Verbose for simple tasks (50+ lines for a basic agent)
        - LangChain dependency brings complexity even if you only want the graph layer
# LangGraph: define a simple agent as a graph
from langgraph.graph import StateGraph, END

graph = StateGraph(AgentState)
graph.add_node("research", research_node)
graph.add_node("write", write_node)
graph.add_node("review", review_node)

graph.add_edge("research", "write")
graph.add_conditional_edges("review", should_revise,
    {"revise": "write", "approve": END})

agent = graph.compile(checkpointer=MemorySaver())
        **Pick LangGraph if:** You're building production agents that need state persistence, complex branching logic, or human approval steps. This is the framework most teams choose when "it has to work reliably at scale."



    ## 2. CrewAI
    Beginner-friendly Role-based YAML config

    CrewAI takes a completely different approach: instead of graphs, you define a *crew* of agents with roles, goals, and backstories. They collaborate on tasks like a team of specialists. It's the most intuitive framework if you're coming from a product or business background.

    ### Why developers pick it

        - **Lowest barrier to entry:** Define agents in YAML, not code
        - **Role-based metaphor:** "Researcher", "Writer", "Editor" &mdash; feels natural
        - **Active community:** Large Discord, frequent updates, growing ecosystem
        - **Built-in delegation:** Agents can hand off work to each other automatically


    ### The trade-offs

        - Less control over execution flow compared to LangGraph
        - Debugging multi-agent interactions can be opaque
        - Performance overhead from the role-playing abstraction layer
# CrewAI: define a research crew
from crewai import Agent, Task, Crew

researcher = Agent(
    role="AI Research Analyst",
    goal="Find the latest AI agent developments",
    backstory="Senior tech journalist with 10 years experience"
)

writer = Agent(
    role="Technical Writer",
    goal="Turn research into clear, actionable content",
    backstory="Dev advocate who writes for a 50k newsletter"
)

crew = Crew(agents=[researcher, writer], tasks=[...])
result = crew.kickoff()
        **Pick CrewAI if:** You want to prototype multi-agent workflows fast, your agents represent distinct roles, or your team includes non-engineers who need to understand the architecture. Best for content pipelines, research workflows, and business automation.



    ## 3. AutoGen (Microsoft)
    Multi-agent chat Conversation patterns Mature

    AutoGen pioneered the multi-agent conversation pattern. Agents talk to each other in structured dialogues &mdash; debates, round-robins, sequential chains. It's the most natural framework for tasks that require consensus or iterative refinement.

    ### Why developers pick it

        - **Conversation-first:** Agents interact through dialogue, not function calls
        - **Group chat:** Multiple agents can debate, vote, and reach consensus
        - **Code execution:** Built-in sandboxed Docker execution for generated code
        - **Flexible patterns:** Sequential, round-robin, or custom conversation flows


    ### The trade-offs

        - Microsoft shifted focus to the broader Agent Framework &mdash; AutoGen is in maintenance mode
        - Conversation-based routing can be unpredictable for deterministic workflows
        - Token costs add up fast when agents chat with each other



        **Note:** Microsoft announced the broader "Microsoft Agent Framework" in late 2025. AutoGen still works and has a large user base, but new investment is going into the platform-level framework. Worth watching, but probably not the best bet for greenfield projects in 2026.




        **Pick AutoGen if:** Your agents need to have multi-party conversations &mdash; group debates, consensus-building, or iterative code review. The conversation patterns are still the most diverse of any framework.



    ## 4. OpenAI Agents SDK
    Official OpenAI Batteries included Simple API

    The successor to the experimental Swarm project. OpenAI's official take on agent building: minimal boilerplate, built-in tools (code interpreter, web browsing, file handling), and native guardrails. If you're already in the OpenAI ecosystem, this is the path of least resistance.

    ### Why developers pick it

        - **Zero-config tools:** Code interpreter, web browsing, and file search out of the box
        - **Handoffs:** Agents can transfer control to specialized sub-agents cleanly
        - **Built-in guardrails:** Input/output validation without extra libraries
        - **Tracing:** Native observability for debugging agent behavior


    ### The trade-offs

        - Locked to OpenAI models &mdash; no Claude, no Gemini, no local models
        - Less customizable than LangGraph for complex state management
        - Relatively new &mdash; ecosystem and community still growing
# OpenAI Agents SDK: simple agent with tools
from agents import Agent, Runner

agent = Agent(
    name="Research Assistant",
    instructions="You help users research AI topics.",
    tools=[web_search, code_interpreter],
    guardrails=[input_guardrail, output_guardrail]
)

result = Runner.run_sync(agent, "What's new in AI agents?")
        **Pick OpenAI Agents SDK if:** You're already using GPT-4o/o3, want the fastest path from zero to working agent, and don't need multi-model support. The built-in tools (especially code interpreter) save hours of integration work.



    ## 5. Claude Agent SDK (Anthropic)
    Sandboxed execution MCP-native Security-first

    Anthropic's framework is built around safety and tool-use. The standout feature is deep integration with the Model Context Protocol (MCP) &mdash; which Anthropic designed &mdash; giving agents a standardized way to connect to external tools and data sources. The 1M-token context window means agents can reason over entire codebases.

    ### Why developers pick it

        - **Security-first:** Sandboxed code execution with Constitutional AI guardrails
        - **MCP integration:** Connect to any MCP-compatible tool server
        - **Massive context:** 1M tokens &mdash; feed it an entire repo and let it work
        - **Extended thinking:** Claude can show its reasoning chain transparently


    ### The trade-offs

        - Locked to Claude models
        - Smaller ecosystem than LangGraph or CrewAI
        - Fewer tutorials and community resources (still early)



        **Pick Claude Agent SDK if:** You're building agents that handle sensitive data, need sandboxed execution, or want MCP-native tool integration. Best-in-class for coding agents and security-sensitive automation.



    ## 6. Google Agent Development Kit (ADK)
    Multimodal Agent-to-Agent Google Cloud

    Google's entry focuses on two differentiators: multimodal agents (text + image + video + audio) and the Agent-to-Agent (A2A) protocol for inter-agent communication across organizational boundaries. If you're building agents that need to process images or video, or agents that talk to other companies' agents, ADK is the framework to watch.

    ### Why developers pick it

        - **Multimodal-native:** Agents that see, hear, and read &mdash; not just text
        - **A2A protocol:** Standardized agent-to-agent communication
        - **Google Cloud integration:** Vertex AI, BigQuery, Cloud Functions built in
        - **Model flexibility:** Gemini-first but supports other models


    ### The trade-offs

        - Heavily tied to Google Cloud ecosystem
        - A2A protocol adoption is still early
        - Less battle-tested than LangGraph for production workloads



        **Pick Google ADK if:** You need multimodal agents, agent-to-agent communication, or you're already deep in Google Cloud. The A2A protocol could be huge &mdash; but it's a bet on the future, not a sure thing today.



    ## 7. Smolagents (Hugging Face)
    Lightweight Code-first Open source

    The minimalist option. Smolagents takes a code-first approach: instead of generating JSON tool calls, agents write Python code directly. This makes them faster (fewer LLM calls) and more transparent (you can read exactly what the agent is doing). Perfect for developers who want maximum control with minimum abstraction.

    ### Why developers pick it

        - **Code-based actions:** Agent writes Python, not JSON &mdash; faster and more flexible
        - **Minimal abstraction:** ~1000 lines of core code, easy to understand and modify
        - **Model-agnostic:** Works with any model via Hugging Face's inference API
        - **Open source:** MIT license, full transparency


    ### The trade-offs

        - No built-in state persistence or checkpointing
        - Less suited for complex multi-agent orchestration
        - You'll build more infrastructure yourself



        **Pick Smolagents if:** You want a lightweight, transparent agent framework you can fully understand and customize. Great for single-agent tasks, research prototypes, and developers who prefer code over configuration.



    ## The Decision Matrix

    Here's how to pick your framework based on what you're building:




                If you need...
                Use this



            Production reliability + state management**LangGraph**
            Multi-agent team collaboration**CrewAI**
            Agent-to-agent conversations**AutoGen**
            Fastest path to a working agent (GPT)**OpenAI Agents SDK**
            Security + sandboxed execution**Claude Agent SDK**
            Multimodal or cross-org agents**Google ADK**
            Lightweight, transparent agents**Smolagents**
            You have no idea (start here)**CrewAI** or **OpenAI**




        **Our take:** There's no single "best" framework. LangGraph leads on production maturity. CrewAI leads on accessibility. The vendor SDKs (OpenAI, Claude, Google) are excellent if you're committed to their ecosystem. The real question isn't "which is best?" &mdash; it's "what am I building, and what trade-offs am I willing to make?"



    ## What We Use at Paxrel

    Our newsletter pipeline &mdash; [AI Agents Weekly](/blog-ai-agent-newsletter.html) &mdash; uses a custom Python pipeline with direct API calls to DeepSeek V3 and Claude. No framework. Why? Because our workflow is linear (scrape &rarr; score &rarr; write &rarr; publish), and adding a framework would be over-engineering.

    That said, if we were building something with branching logic, human approvals, or multi-agent collaboration, we'd reach for LangGraph first. It's the framework we'd trust with a production workload today.

    ## Stay in the Loop

    The AI agent ecosystem moves fast. We track every framework update, new tool, and production case study in our free newsletter.


        ### AI Agents Weekly
        The top AI agent news, curated 3x/week. Tools, frameworks, launches, and what actually works in production.

        [Subscribe Free](https://buttondown.com/paxrel)


    You can also grab our free resources:


        - [Top 10 AI Agent Tools 2026](/top-10-ai-agent-tools-2026.pdf) (14-page PDF guide)
        - [AI Agent Stack Cheat Sheet](/ai-agent-stack-cheatsheet-2026.pdf) (2-page reference card)


    ## Related Articles

        - [How to Run Autonomous AI Agents with Claude Code](/blog-claude-code-autonomous-agents.html)
        - [What Is MCP (Model Context Protocol)?](/blog-mcp-model-context-protocol.html)
        - [How to Build an AI Agent in 2026: Step-by-Step Guide](/blog-how-to-build-ai-agent.html)
        - [What Are AI Agents? The Complete Guide](/blog-what-are-ai-agents.html)

Want more AI agent content?

Originally published on paxrel.com

How to Deploy an AI Agent to Production: VPS, Docker & Serverless (2026)

How to Deploy an AI Agent to Production: VPS, Docker & Serverless (2026)

    Your agent works on your laptop. Great. Now how do you make it run 24/7 without you babysitting it? Deployment is where most AI agent projects die &mdash; not because the agent doesn't work, but because nobody figured out how to keep it running reliably.

    This guide covers three deployment approaches (VPS, Docker, serverless), with real configs, cost breakdowns, and the monitoring you need to sleep at night while your agent works.

    ## Choosing Your Deployment Model



            Approach
            Best For
            Monthly Cost
            Complexity
            Always-On?


            **VPS (bare metal)**
            24/7 autonomous agents
            $5-20
            Medium
            Yes


            **Docker + VPS**
            Reproducible, multi-agent
            $10-30
            Medium-High
            Yes


            **Serverless (Lambda/Cloud Run)**
            Event-triggered agents
            $1-50 (pay-per-use)
            Low-Medium
            No (triggered)


            **Managed platforms**
            No-ops teams
            $20-200
            Low
            Varies



    ## Option 1: VPS Deployment (What We Use)

    The simplest path to a 24/7 agent. Rent a virtual server, install your agent, set up a process manager, and let it run.

    ### Step 1: Choose a VPS Provider



            Provider
            Cheapest Plan
            Specs
            Best For


            **Hetzner**
            $4.50/mo
            2 vCPU, 4GB RAM, 40GB SSD
            Best value in EU


            **DigitalOcean**
            $6/mo
            1 vCPU, 1GB RAM, 25GB SSD
            Simple UI, good docs


            **Vultr**
            $6/mo
            1 vCPU, 1GB RAM, 25GB SSD
            Global locations


            **Contabo**
            $6.50/mo
            4 vCPU, 8GB RAM, 50GB SSD
            Most specs per dollar




        **What Paxrel uses:** A Hetzner CX22 ($5.50/mo) with 2 vCPU, 4GB RAM. Runs our full agent stack: newsletter pipeline, social media automation, web scraping, and Reddit karma builder &mdash; all on one server.


    ### Step 2: Initial Server Setup
# SSH into your new server
ssh root@your-server-ip

# Create a non-root user
adduser agent
usermod -aG sudo agent

# Install essentials
apt update && apt install -y python3 python3-pip python3-venv git curl

# Switch to agent user
su - agent

# Clone your agent code
git clone https://github.com/your-org/your-agent.git
cd your-agent

# Set up Python environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# Create environment file for credentials
cat > .env > logs/pipeline.log 2>&1

# Social media posting: Every 6 hours
0 */6 * * * cd /home/agent/your-agent && .venv/bin/python3 post_tweet.py >> logs/twitter.log 2>&1

# Daily monitoring report
30 9 * * * cd /home/agent/your-agent && .venv/bin/python3 monitoring.py >> logs/monitoring.log 2>&1
    ## Option 2: Docker Deployment

    Docker adds reproducibility and isolation. Especially useful when running multiple agents or when your agent has complex dependencies.
# Dockerfile
FROM python:3.12-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends 
    curl git && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy agent code
COPY . .

# Non-root user for security
RUN useradd -m agent
USER agent

CMD ["python3", "agent.py"]
# docker-compose.yml
version: '3.8'

services:
  agent:
    build: .
    restart: always
    env_file: .env
    volumes:
      - ./data:/app/data      # Persist agent memory/state
      - ./logs:/app/logs       # Persist logs
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '1.0'
    healthcheck:
      test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:8080/health')"]
      interval: 60s
      timeout: 10s
      retries: 3

  # Optional: vector database for RAG
  chromadb:
    image: chromadb/chroma:latest
    restart: always
    volumes:
      - chroma_data:/chroma/chroma
    ports:
      - "8000:8000"

volumes:
  chroma_data:
# Deploy
docker compose up -d

# View logs
docker compose logs -f agent

# Update agent
git pull && docker compose build && docker compose up -d
    ## Option 3: Serverless Deployment

    For agents triggered by events (webhook, email, schedule) rather than running continuously. Pay only when the agent runs.

    ### AWS Lambda + EventBridge
# handler.py
import json
import boto3

def lambda_handler(event, context):
    """Triggered by EventBridge cron or API Gateway webhook"""

    # Your agent logic here
    from agent import run_agent
    result = run_agent(event)

    return {
        'statusCode': 200,
        'body': json.dumps(result)
    }
# serverless.yml (Serverless Framework)
service: ai-agent

provider:
  name: aws
  runtime: python3.12
  timeout: 300  # 5 minutes max
  memorySize: 512
  environment:
    OPENAI_API_KEY: ${ssm:/ai-agent/openai-key}

functions:
  newsletter:
    handler: handler.lambda_handler
    events:
      - schedule: cron(0 8 ? * MON,WED,FRI *)  # Mon/Wed/Fri 8am
  webhook:
    handler: handler.lambda_handler
    events:
      - httpApi:
          path: /webhook
          method: post
    ### Google Cloud Run
# For longer-running agents (up to 60 min)
gcloud run deploy ai-agent 
  --source . 
  --region us-central1 
  --memory 1Gi 
  --timeout 3600 
  --set-env-vars "OPENAI_API_KEY=sk-..." 
  --no-allow-unauthenticated
            Platform
            Max Runtime
            Cold Start
            Cost per Run


            AWS Lambda
            15 minutes
            1-5 seconds
            $0.0001-0.01


            Google Cloud Run
            60 minutes
            2-10 seconds
            $0.001-0.05


            Vercel Functions
            5 minutes (pro: 15)

            $0.0001-0.005


            Cloudflare Workers
            30 seconds (free)

            $0.00005



    ## Monitoring Your Deployed Agent

    A deployed agent without monitoring is a liability. Here's the minimum monitoring stack:

    ### Health Check Endpoint
from flask import Flask, jsonify
import psutil

app = Flask(__name__)

@app.route('/health')
def health():
    return jsonify({
        "status": "healthy",
        "uptime_hours": get_uptime(),
        "memory_mb": psutil.Process().memory_info().rss / 1024 / 1024,
        "last_run": get_last_run_timestamp(),
        "errors_24h": get_error_count(hours=24),
        "api_balance": check_api_balance()
    })
    ### Alert System
import requests

def send_alert(message, level="warning"):
    """Send alert via Telegram/Slack/email"""
    if level == "critical":
        # Telegram for immediate attention
        requests.post(
            f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
            data={"chat_id": OWNER_ID, "text": f"🚨 {message}"}
        )
    else:
        # Slack webhook for non-critical
        requests.post(SLACK_WEBHOOK, json={"text": f"⚠️ {message}"})

# Alerts to configure:
# - Agent crash / restart
# - API balance below threshold
# - Error rate spike (3+ errors in 10 min)
# - Agent stuck (no activity for 2+ hours)
# - Cost spike (daily spend > 2x average)
    ### Log Management
import logging
from logging.handlers import RotatingFileHandler

# Structured logging
handler = RotatingFileHandler(
    'logs/agent.log',
    maxBytes=10_000_000,  # 10MB per file
    backupCount=5          # Keep 5 rotated files
)
handler.setFormatter(logging.Formatter(
    '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
))

logger = logging.getLogger('agent')
logger.addHandler(handler)

# Log every significant action
logger.info("Scraping 12 RSS feeds")
logger.info("Scored 97 articles, top score: 28")
logger.warning("API rate limited, retrying in 30s")
logger.error("Beehiiv publish failed: 401 Unauthorized")
    ## Production Hardening Checklist

    ### Security

        - API keys in environment variables or secrets manager, never in code
        - Non-root user for the agent process
        - Firewall: only allow SSH (22) and necessary ports
        - SSH key auth only, disable password login
        - Auto-update OS security patches (`unattended-upgrades`)


    ### Reliability

        - Process manager with auto-restart (systemd, Docker restart policy)
        - Graceful shutdown handling (catch SIGTERM, finish current task)
        - Exponential backoff on API errors (not infinite retry loops)
        - Circuit breaker for external services (stop calling after N failures)
        - Daily backup of agent state/memory to external storage


    ### Cost Control

        - Daily API spend limit with hard cutoff
        - Max steps per agent run (prevent infinite loops)
        - Token counting before API calls (reject oversized prompts)
        - Alert when daily spend exceeds 2x average
        - Weekly cost report to the team


    ## Deployment Patterns by Use Case



            Agent Type
            Best Deployment
            Why


            24/7 autonomous agent
            VPS + systemd
            Always-on, persistent state


            Scheduled pipeline
            VPS + cron or serverless
            Runs on schedule, sleeps between


            Webhook-triggered
            Serverless (Lambda/Cloud Run)
            Pay-per-use, auto-scales


            Multi-agent system
            Docker Compose on VPS
            Isolated containers, shared network


            Customer-facing chatbot
            Cloud Run or managed platform
            Auto-scale with traffic


            Development/testing
            Local Docker
            Reproducible environment



    ## Key Takeaways


        - **VPS + systemd is the simplest path** for always-on agents. $5-15/month, full control, works for 90% of use cases.
        - **Docker adds value** when you have complex dependencies, multiple agents, or need reproducibility across environments.
        - **Serverless is cheaper for sporadic workloads** but has runtime limits (15 min for Lambda) that don't suit long-running agents.
        - **Monitoring is not optional.** Health checks, alerts, and log rotation are the minimum. An unmonitored agent will fail silently.
        - **Security basics matter.** Non-root user, env vars for secrets, firewall, SSH keys. Takes 30 minutes, prevents disasters.
        - **Start simple, scale later.** A $5 VPS with cron jobs is a perfectly valid production deployment. Don't over-engineer until you need to.



        ### Deploy With Confidence
        Our AI Agent Playbook includes Dockerfiles, systemd configs, monitoring templates, and deployment checklists for production agents.

        [Get the Playbook &mdash; $29](https://paxrel.gumroad.com/l/ai-agent-playbook)



        ### Stay Updated on AI Agents
        Deployment patterns, infrastructure tips, and production war stories. 3x/week, no spam.

        [Subscribe to AI Agents Weekly](/newsletter.html)

Want more AI agent content?

Originally published on paxrel.com