6 n8n Workflow Patterns for AI Automation (Lead Gen, Enrichment, RAG, Self-Healing)

6 n8n workflow patterns for AI automation (with real examples)

After building n8n automations for over a year across different use cases, I started recognizing the same core patterns appearing in every serious AI automation setup. Here are the six that show up in every production workflow I have built.

Pattern 1: Webhook → LLM classify → route

The classic intake pattern. An inbound webhook (from email, form, Slack, API call) triggers an LLM to classify the payload, then routes it to the right downstream workflow.

Webhook trigger
  → HTTP Request (normalize payload)
  → Claude/GPT-4o node (classify: type, urgency, intent)
  → Switch node (route by classification)
    → Branch A: create Linear ticket
    → Branch B: send Slack alert
    → Branch C: log and discard

Use case: Customer support triage, inbound lead routing, webhook fan-out

Pattern 2: Cron → scrape → summarize → Slack

The daily briefing pattern. Fires every morning, scrapes target sources, runs an LLM summarizer, posts to Slack.

Schedule trigger (every day 07:00)
  → HTTP Request nodes (scrape sources)
  → Merge node
  → Claude node (summarize + extract key signals)
  → Slack node (post to #briefings)

Use case: Competitor monitoring, keyword alerts, market intelligence, news briefings

Pattern 3: CRM event → AI enrich → update

Fires when a new lead enters HubSpot/Pipedrive. Enriches with public data, generates an AI-written lead summary, writes it back to the CRM.

HubSpot trigger (new contact)
  → HTTP Request (fetch enrichment data: LinkedIn, Clearbit, Apollo)
  → Claude node (synthesize: company context, fit score, outreach angle)
  → HubSpot node (update contact with AI summary)
  → Slack node (notify sales rep)

Use case: Sales automation, lead enrichment, SDR assist

Pattern 4: Document → chunk → embed → vector store

Local RAG without a managed service. Process documents into a searchable vector store.

File trigger (new upload to folder)
  → Code node (read + chunk into ~512 token segments)
  → Embeddings node (OpenAI/Cohere)
  → Vector store node (Qdrant, Pinecone, Supabase pgvector)
  → Response: "Document indexed, 42 chunks stored"

Use case: Internal knowledge base, contract analysis, support doc search

Pattern 5: Error → LLM diagnose → create ticket

Self-healing workflows. When a critical workflow errors, an LLM diagnoses the error and creates a Linear/GitHub ticket automatically.

Error trigger (workflow failed)
  → Code node (format error: workflow, node, message, context)
  → Claude node (diagnose: likely cause + suggested fix)
  → Linear node (create issue with LLM diagnosis)
  → Slack node (alert #on-call)

Use case: Monitoring, incident response, workflow reliability

Pattern 6: Trigger → AI draft → human approve → send

Best of both worlds: AI writes, human approves. The approval step can be Slack, email, or a custom web form.

Schedule trigger
  → Claude node (draft: email / social post / report)
  → Wait node (pause for human review)
    → Slack node ("Draft ready — approve or edit?")
  → If approved: send via Gmail / Postmark / Resend
  → If rejected: loop back to Claude with feedback

Use case: Outbound email sequences, social media, weekly reports

What makes these work in production

A few things separate toy demos from workflows that run reliably for months:

Error handling at every HTTP node. Set “Continue on fail” and add an error branch. Unhandled HTTP failures will silently break your workflow.

Declare your LLM model explicitly. Do not use defaults — specify the exact model (claude-sonnet-4-6, gpt-4o, etc.) so updates do not surprise you.

Use credential variables, not hardcoded values. n8n’s built-in credentials manager handles rotation gracefully.

Separate triggering from processing. Webhook receivers should be thin — just validate and hand off. Do the heavy processing in a second workflow called via n8n’s API.

The part that is tedious

Building these patterns from scratch is not hard — it is just slow. I spent most of my first six months recreating the same structural patterns across different clients and use cases.

I packaged up 350 of these into a production workflow library: 12 integration lanes, all LLMs pre-configured and swappable with one node edit.

350 n8n AI Workflow Templates on Gumroad — use BLNCRAFT20 for 20% off launch week.

What patterns are you using in your n8n automations? Always curious what combinations people find most useful.

The Python Data Analytics Handbook: A Guide for Beginners

Introduction

Python is a computer programming language often used to build websites and software, automate tasks, and conduct data analysis.

Python allows analysts to handle the entire data lifecycle, from collecting and cleaning raw data to performing complex statistical modeling and creating interactive visualizations.

Why Python is popular in data analytics

Python was designed to be human-friendly; easy to read and write.
It has become the most popular tool in data analytics because it’s approachable but powerful. It does the heavy lifting for you, allowing you to focus on the story your data is telling rather than struggling with the tools themselves.
For a beginner, Python isn’t just another skill, it’s the bridge that takes you from being overwhelmed by numbers to actually making sense of them.

Essential Python Libraries for Data Analysis

In Python, a library is a collection of pre-written code that you can use to perform specific tasks without writing everything from scratch.
These libraries act like ready-made toolkits, each designed for a specific part of the workflow, from data manipulation to visualization. Let’s look at the most essential ones every analyst should know:

1. Pandas (Python Data Analysis)
Pandas is an open-source tool for data manipulation.
It is the most popular Python library for working with tabular data. It is similar to Excel sheets, but with far more power and flexibility. You can use it for:

  • Easy data loading from CSV, Excel, SQL, and JSON.
  • Filtering, grouping, and merging datasets.
  • Handling missing data and time-series analysis.

Example:

import pandas as pd

df = pd.read_csv('student.csv')  # load the file
print(df['Math'])  # print just the math column

2. NumPy (Numerical Python)
NumPy is the foundational library for scientific computing in Python.
The NumPy library contains multidimensional array data structures, such as the homogeneous, N-dimensional ndarray, and a large library of functions that operate efficiently on these data structures.

Example:

import numpy as np

prices = np.array([5, 10, 15]) # list of numbers
new_prices = prices + 2   # add 2 to every number
print(new_prices)  # print new list
  • SciPy (Scientific Python):
    Extends NumPy for advanced scientific and engineering computations.

3. Matplotlib
Matplotlib is the most widely used Python library for creating static, animated, and interactive data visualizations. It has become a fundamental tool in data science and machine learning for exploring patterns and trends.

Example:

import matplotlib.pyplot as plt

students = ["Alex", "Mary", "John", "Anna"]
grades = [85, 92, 58, 95]

plt.bar(students, grades, color="skyblue") # create a bar chart

plt.title("Student Math Grades")  # add labels to chart
plt.xlabel("Student Name")
plt.ylabel("Math Score")

plt.show()  # display chart

4. Seaborn
While Matplotlib handles the basics, Seaborn builds on it with elegant styles and simpler syntax for statistical visualizations. It is perfect for visualizing relationships and distributions in your data. Seaborn automatically handles color palettes, aesthetics, and complex charts.

Example:

import matplotlib.pyplot as plt  # handles the graph layout and displays it on your screen
import pandas as pd  # opens and structures your student.csv file
import seaborn as sns  # colors and styles the bars automatically based on your columns

df = pd.read_csv("student.csv")  # load student file

sns.barplot(data=df, x="Name", y="Score", hue="Subject", palette="deep") # draw the chart (Seaborn automatically reads the columns from df)

plt.title("Student Performance Comparison")  # add titles 
plt.show()  # show the window

5. Scikit-learn (often imported as sklearn)
This is a Python open-source machine learning library. Built on top of core scientific libraries like NumPy, SciPy, and Matplotlib, it provides simple and efficient tools for predictive data analysis.
Note: Scikit-learn requires data to be formatted as numerical arrays to perform its calculations.

Example:

import numpy as np  # it formats data into a 2D column grid so the machine learning model can read it
from sklearn.linear_model import LinearRegression

# classes attended vs. final Score
classes = np.array([[10], [20], [30]])
scores = np.array([40, 60, 80])  # the computer sees that more classes = higher score

model = LinearRegression() # create and train the model
model.fit(classes, scores)

#  predict the score for a student who attended 25 classes
predicted_score = model.predict([[25]]) 
print(predicted_score)

6. Requests
Requests library is a simple and powerful tool to send HTTP requests and interact with web resources. It allows you to easily send get, post, put, delete, patch, head requests to web servers, handle responses, and work with REST APIs and web scraping tasks.

Example:

import requests

url = "githubusercontent.com"  # connect to the raw student data file link on GitHub
response = requests.get(url)

print(response.text)  # print the raw data content from the internet
  • Beautiful Soup: For parsing HTML and XML data.
  • Django & Flask: Frameworks used to build powerful web applications.
  • TensorFlow & PyTorch: Leading frameworks for deep learning and neural networks.

How Python is used to clean, analyze, and visualize data

Data cleaning is a critical step in any data analysis or machine learning project.
Essential Python-specific steps include: handling missing values, standardizing data types, removing duplicates, and identifying outliers to ensure analysis accuracy.
Here are some best practices to keep in mind as you streamline your data cleaning process:

  • Store raw data separately
    Always keep the original!
    This is the number one most important tip when cleaning data. Keep a copy of the raw data files separate from the cleaned and processed versions. This ensures that you always have a reference point and can easily revert to the original data if needed.

  • Document your data-cleaning code
    Add comments to your code to explain the purpose of each cleaning step and any assumptions made.

  • Look out for unintended consequences
    Make sure your data cleaning efforts aren’t significantly changing the distribution or introducing any unintended biases. Repeated data exploration after your cleaning efforts can help ensure you are on the right track.

  • Keep a data cleaning log
    If you have a long cleaning process or one that is automated, you may want to maintain a separate document where you record the details of each cleaning step.
    Details such as the date, the specific action taken, and any issues encountered may be helpful down the road.

  • Write reusable functions
    Identify common data cleaning tasks and encapsulate them into reusable functions. This allows you to apply the same cleaning steps to multiple datasets. This is especially helpful if you have company-specific abbreviations you want to map.

Moving to the Analysis stage, this is the bridge where you extract meaning from your cleaned rows.
Using tools like Pandas, you calculate averages, find the highest and lowest values, and group items into categories to see how they compare.
This process takes a giant pile of cleaned data and summarizes it to a few important facts, so you know exactly what to show in your final charts.

The final phase Visualization, is where you turn those rows of numbers into insights that can easily be understood.
This is achieved by using Matplotlib for foundational control, Seaborn for polished statistical graphics, and Plotly for interactivity.
The process involves aggregating your data into summaries, selecting a chart type that fits the relationship and refining the output with clear labels and titles to ensure the data is immediately understood by the viewer.

An example use case for the process:
Python sorts through a Kenyan bank’s records and organizes it automatically:

  • Cleaning: fixing messy M-Pesa transaction texts and converting USD deposits into Kenya Shillings automatically.
  • Analyzing: calculating which customers qualify for loans based on how regularly a customer’s balance in M-Shwari or KCB M-Pesa is growing or shrinking over time.
  • Visualizing: uses maps to show where to open new shops and gauges that warn managers the moment an ATM runs out of cash.

Real-world examples of Python in data analytics

1. Data Science and Data Analysis

Python is the leading language in data science. It offers powerful tools for data manipulation, visualization, and machine learning.

Use Case Example: A data analyst uses Pandas to clean large CSV files and visualize sales trends with Matplotlib.

Libraries:

  • Pandas: Data cleaning and manipulation
  • NumPy: Numerical computation
  • Matplotlib / Seaborn: Data visualization

2. Education & Research

Python is the preferred language in academia and research due to its simplicity and vast scientific libraries.

Use Case Example: Researchers use Python for data modeling in climate studies or biological simulations.

Libraries:

  • Jupyter Notebooks for documentation and experiments
  • SymPy for symbolic math
  • SciPy for computations

3. Healthcare

Python is used in healthcare for image-based diagnostics and predictive analysis, to enable health care professionals determine the information they need to make the best decision possible regarding treatment plans.

Use Case Example: Algorithms help detect bone fractures, tumors, and early-stage breast cancer from mammograms.

Libraries:

  • NumPy & Pandas to clean, filter, and structure tabular patient metrics, lab stats, and vitals.
  • Scikit-learn to train standard medical risk-assessment machine learning algorithms.
  • TensorFlow & PyTorch to power deep learning applications that automatically spot tumors in diagnostic imagery.

4. Machine Learning & Artificial Intelligence

Python dominates ML and AI development. Its ecosystem allows you to train, test, and deploy intelligent models with ease.

Use Case Example: Netflix uses Python-based recommendation algorithms to personalize movie suggestions.

Libraries:

  • Scikit-learn for traditional ML models
  • TensorFlow and PyTorch for deep learning
  • spaCy for NLP tasks

5. Web Development

Python simplifies web development with frameworks like Django, Flask, and FastAPI. These tools help build scalable and secure web applications quickly.

Use Case Example:

  • Instagram: Uses Django for scalability.
  • Pinterest: Built with Flask for flexibility.

Libraries:

  • Django for rapid web app development
  • Flask for lightweight APIs
  • FastAPI for high-performance web services.

6. Web Scraping and Data Extraction

Python helps gather and analyze online information at scale. Businesses use it for price tracking, content aggregation, and market research.

Use Case Example: E-commerce teams use Python scripts to monitor competitor pricing in real time.

Libraries:

  • Requests
  • BeautifulSoup
  • Scrapy

7. Software Development and Testing

Python is used for backend software development and test automation.

Use Case Example: Developers use Python to test APIs automatically after every code change.

Frameworks:

  • PyTest and Unittest for testing
  • Sphinx for documentation
  • Buildbot for CI/CD pipelines

8. Desktop GUI Applications

Python supports GUI-based applications that run across platforms.

Use Case Example: Simple apps like file explorers or calculators are often built using Tkinter.

Libraries:

  • Tkinter for basic GUIs
  • PyQt and Kivy for advanced interfaces

9. Internet of Things (IoT)

Python connects hardware and sensors in IoT systems. It’s used for collecting and processing real-time data.

Use Case Example: Home automation systems that monitor temperature and lighting using Raspberry Pi.

Tools and Platforms:

  • Raspberry Pi
  • MicroPython
  • Adafruit Python SDKs

10. Game Development

Python supports simple and mid-level game creation. It’s ideal for building prototypes, 2D games, and educational projects.

Use Case Example: Several well-known titles have used Python for their core logic or extensive modding capabilities like: The Sims 4, Battlefield 2, Pacman, Sudoku.

Tools and Frameworks:

  • Pygame for 2D games
  • Panda3D for 3D graphics
  • Arcade for modern designs

Why beginners should learn Python

Python is the perfect starting point because its syntax is as clear as plain English, allowing you to focus on logic rather than fighting complex code. Its massive global community means you’ll never get stuck, and its incredible versatility allows you to transition easily into high-paying fields like data science, AI, or web development.
By using powerful libraries that handle the heavy lifting for you, Python lets you build professional-grade projects like automating your bank statements or analyzing electricity bills, faster than almost any other language.

Understanding Solana’s Account Model: A Web2 Developer’s Guide

Solana as a Giant Database
Think of Solana as a massive, globally distributed database where every piece of data lives in its own record called an account. Each account has a unique 32-byte address (like a primary key in a database) and contains five fields, similar to columns in a database table:

• lamports: The account’s SOL balance (think of this as a wallet balance field)
• data: A byte array storing either code or state (like a BLOB field)
• owner: Which program controls this account (like foreign key ownership)
• executable: A flag marking whether this account contains runnable code
• rent_epoch: When the account needs to maintain its minimum balance

Solana’s account model represents a fundamental departure from blockchain architectures like Ethereum. In Ethereum, “contract account” are separate from “externally owned account”. Solana uses a single account model for everything: wallets, programs, data storage.

Programs vs. Data: Microservices Architecture
In traditional web2, you might have microservices that process data stored separately in databases. Solana works similarly:
• Programs are like stateless microservices—they contain executable code but store no data themselves
• Data accounts are like database records that programs read from and write to
For example, when you use an SPL token (Solana’s version of ERC-20), the token program is like a shared API service. Your token balances aren’t stored in your main wallet—instead, you have separate “token account” records for each token type you own. One account for USDC, another for any other token.
This is unlike Ethereum where smart contracts contain both code and state and is key to grasping why Solana achieves such high throughput.

Ownership: Role-Based Access Control
Only an account’s owner program can modify its data or decrease its balance. This is like role-based access control (RBAC) in traditional applications—only authorized services can write to specific database tables.
Any program can add funds to any account (like anyone can send you money), but only the owner can modify the account’s internal state.

The Parallel Execution Advantage: Lock-Free Concurrency
Here’s where Solana gets really interesting for performance. In web2, imagine a REST API where every request must explicitly declare which database tables and rows it will read or modify before execution.
With this information upfront, your database could:
• Execute requests that touch different records simultaneously (parallel execution)
• Only serialize requests that conflict (touch the same records)
That’s exactly what Solana does. Every transaction declares its account dependencies upfront, allowing the runtime to schedule thousands of non-conflicting transactions in parallel—similar to how modern databases use optimistic concurrency control.
In contrast, imagine a web2 system where every API request could potentially modify any part of a single giant JSON object (global state). You’d have to process every request sequentially to avoid race conditions. That’s the bottleneck Solana avoids.

Rent: Storage Cost Management
Initially, Solana charged “rent” for accounts to discourage state bloat—like cloud storage fees. Now, accounts must maintain a minimum balance to remain rent-exempt, similar to how some SaaS platforms require a minimum payment to keep your account active.

Seguridad LMS después del incidente del LMS Canvas

Cuando falla un sistema de gestión de aprendizaje, el problema rara vez se limita a una página de inicio de sesión.

Las clases se interrumpen. Los profesores pierden acceso a materiales de curso. Los estudiantes pierden entregas, mensajes, calificaciones e instrucciones. Los administradores tienen que responder preguntas antes de tener todos los hechos claros. Por eso el incidente de seguridad de 2026 relacionado con el LMS Canvas, de Instructure, importa más allá de un proveedor o una plataforma específica.

La lección útil no es que un LMS sea bueno y otro sea malo. Eso sería demasiado simple, y en seguridad las conclusiones simples suelen salir caras. La lección es que un LMS es infraestructura crítica para educación y capacitación. Debe operarse con la misma disciplina que se espera de cualquier sistema que concentra identidad, comunicación, evaluaciones, integraciones y continuidad operativa.

Para organizaciones que usan el LMS Moodle™, o que evalúan un servicio gestionado para el LMS Moodle, la pregunta no debería ser solo si la plataforma es open source, comercial, popular u hospedada. La mejor pregunta es: ¿quién es responsable de mantener el LMS actualizado, monitoreado, respaldado, revisado y recuperable cuando algo sale mal?

¿Qué pasó en el incidente del LMS Canvas?

Según la actualización pública de Instructure, la empresa detectó actividad no autorizada en el LMS Canvas el 29 de abril de 2026, revocó el acceso del actor no autorizado, inició una investigación y contrató especialistas forenses externos. Instructure reportó después un segundo evento de acceso no autorizado el 7 de mayo de 2026, relacionado con otra vulnerabilidad en el LMS Canvas. La empresa indicó que puso temporalmente fuera de línea el LMS Canvas, aplicó medidas de contención y confirmó que la actividad se realizó por medio de cuentas Free-For-Teacher. (Instructure)

Instructure indicó que los campos de datos involucrados incluían nombres de usuario, correos electrónicos, nombres de cursos, información de matrícula y mensajes. También señaló que, según sus hallazgos hasta ese momento, los datos centrales de aprendizaje, incluyendo contenido de cursos, entregas y credenciales, no fueron comprometidos. (Instructure)

AP News reportó que el grupo ShinyHunters se atribuyó la responsabilidad y amenazó con publicar datos relacionados con casi 9.000 centros educativos y 275 millones de personas. AP también reportó que Instructure dijo haber llegado a un acuerdo con el actor no autorizado para la devolución y destrucción de los datos, aunque reconoció la incertidumbre que siempre queda cuando se trata con cibercriminales. (AP News)

Esa última parte importa. Incluso cuando un proveedor contiene un incidente, rota credenciales, refuerza sistemas y se comunica con sus clientes, el impacto práctico continúa. Las instituciones todavía necesitan entender la exposición, vigilar posibles ataques de phishing, apoyar a los usuarios y revisar sus propias integraciones y actividad administrativa.

El problema real es el riesgo operativo del LMS

Un LMS no es solo un sitio web de cursos. Es donde ocurre la operación del aprendizaje.

Un LMS típico puede incluir usuarios, roles, contenido de cursos, tareas, calificaciones, entregas, mensajes, reportes, asistencia, certificados, integraciones de pago o matrícula, proveedores de identidad, herramientas de video, servicios antiplagio, analítica y automatización. Parte de esa información es sensible. Parte es crítica para la operación. Parte puede parecer inofensiva por separado, pero se vuelve útil para phishing, suplantación o ingeniería social cuando se combina con otros datos.

Por eso, la seguridad LMS no debería reducirse a una casilla que dice “la plataforma es segura”. Ninguna plataforma seria puede prometer eso. El mejor estándar es operativo: el LMS debe mantenerse, observarse, documentarse y prepararse para recuperación.

Esto aplica al LMS Canvas. Aplica al LMS Moodle. Aplica a cualquier plataforma de aprendizaje amplia que se convierte en parte de la rutina diaria de una institución o empresa.

No es el LMS Canvas vs el LMS Moodle

Krestomatio trabaja con el LMS Moodle, pero sería deshonesto convertir este incidente en un argumento simple de “el LMS Canvas vs el LMS Moodle”.

No hay evidencia pública de que el LMS Moodle haya sido afectado directamente por el incidente del LMS Canvas. Al mismo tiempo, el LMS Moodle es software amplio usado por muchas organizaciones, con plugins, temas, integraciones, roles, tareas en segundo plano, almacenamiento, bases de datos e infraestructura alrededor. Como cualquier sistema serio, requiere actualizaciones, revisiones, respaldos y administración clara.

Moodle publica procedimientos de seguridad para divulgación responsable y liberaciones coordinadas, y mantiene anuncios de seguridad para administradores y sitios registrados. Su documentación para administradores y su proceso de seguridad apuntan en la misma dirección: mantener la plataforma actualizada, usar HTTPS, revisar la configuración, mantener respaldos y probar restauraciones. (Moodle) (MoodleDocs) (Moodle)

Entonces la diferencia práctica no es que el LMS Moodle elimine mágicamente el riesgo. La diferencia práctica está en cómo se opera la plataforma.

Un servicio gestionado para el LMS Moodle no es solo hosting

Muchos problemas de LMS empiezan con una decisión razonable: “solo necesitamos un servidor, un dominio y la plataforma instalada”. Eso puede ser suficiente para empezar. No es suficiente cuando el LMS se vuelve importante.

El hosting responde dónde corre el software. La operación gestionada responde cómo se mantiene saludable la plataforma en el tiempo.

Un servicio gestionado para el LMS Moodle debería incluir control de versiones, actualizaciones, estrategia de respaldos, monitoreo, pruebas de restauración, revisión de plugins, hardening de seguridad, automatización de infraestructura y procedimientos de respuesta. Debería reducir la cantidad de trabajo manual que depende de que una persona ocupada recuerde cada detalle.

En Krestomatio, el LMS Moodle se trata como una plataforma gestionada, no como una instalación aislada. El objetivo es simple: ayudar a instituciones y empresas a enfocarse en enseñar y capacitar, mientras la operación de la plataforma se maneja con disciplina.

Eso incluye:

  • Actualizaciones del LMS Moodle y de su infraestructura, con control sobre versiones y componentes relevantes.
  • Revisión y validación de plugins, porque los plugins agregan valor, pero también amplían la superficie de riesgo.
  • Respaldos y planificación de restauración, porque un respaldo que no se puede restaurar es solo una buena intención.
  • Monitoreo y observabilidad, para detectar antes comportamientos inusuales y problemas operativos.
  • Despliegues reproducibles, usando imágenes versionadas y definiciones de infraestructura en lugar de cambios improvisados en servidores.
  • Separación de componentes y permisos, para que la aplicación, base de datos, caché, almacenamiento y automatización no dependan de privilegios innecesarios.
  • Gestión controlada de secretos, evitando credenciales en manifiestos, logs, repositorios o salidas de automatización.
  • Políticas de red y aislamiento entre cargas, reduciendo exposición entre componentes de la plataforma.
  • Validación con CI/CD, para hacer los cambios más consistentes y más fáciles de revisar.

Estas prácticas no eliminan el riesgo. Nada honesto en seguridad promete eso. Reducen errores evitables, mejoran la trazabilidad y hacen que la respuesta sea menos caótica cuando hay presión.

La seguridad es una responsabilidad compartida

Un proveedor gestionado puede encargarse de una parte grande del trabajo técnico: infraestructura, actualizaciones, respaldos, monitoreo, automatización de despliegues, revisión de plugins, hardening y soporte de recuperación.

Pero la institución todavía conserva decisiones importantes.

Debe decidir quién puede administrar cursos, quién puede crear usuarios, cómo se integra la identidad, qué datos se almacenan, cuánto tiempo se retienen, cuáles plugins se permiten, cómo se reporta actividad sospechosa y quién se comunica con profesores y estudiantes durante un incidente.

Los usuarios finales también tienen un papel, aunque sea más limitado: proteger credenciales, evitar compartir cuentas, prestar atención a mensajes sospechosos y reportar comportamientos extraños rápidamente.

Cuando esas responsabilidades no están claras, la respuesta a incidentes se vuelve lenta. Cuando se definen con anticipación, la organización tiene más oportunidad de actuar con calma y prioridad.

Costo-beneficio: lo barato puede salir caro

El costo visible de un LMS es fácil de contar: hosting, dominio, horas de soporte y quizá algunos plugins.

El costo oculto aparece después: actualizaciones urgentes, plugins rotos, respaldos fallidos, inicios de sesión sospechosos, cambios en integraciones de identidad, crecimiento de almacenamiento, problemas de rendimiento, solicitudes de auditoría y respuesta a incidentes. No son problemas teóricos. Son realidades operativas normales cuando el LMS se vuelve parte de la organización.

Por eso la pregunta de costo-beneficio no debería ser solo: “¿Cuánto cuesta tener el LMS Moodle funcionando?”

La mejor pregunta es: “¿Cuánto cuesta operar bien el LMS Moodle?”

Un LMS bien gestionado no garantiza que nunca habrá incidentes. Sí mejora lo básico: menos riesgos innecesarios, mejor continuidad, responsabilidad más clara y más opciones cuando hay que actuar.

Una lista práctica de seguridad LMS

Para organizaciones que revisan la operación del LMS Moodle después del incidente del LMS Canvas, una primera revisión útil es:

  • Confirmar la versión del LMS Moodle y el proceso de actualización.
  • Revisar los plugins instalados, sus mantenedores y su historial de actualizaciones.
  • Verificar que los respaldos corran automáticamente y que la restauración se pruebe.
  • Revisar cuentas administradoras, roles y accesos privilegiados.
  • Confirmar HTTPS, configuración segura de sesiones y configuración de correo.
  • Revisar integraciones con proveedores de identidad y tokens de aplicaciones externas.
  • Revisar logs, monitoreo, alertas y contactos de respuesta a incidentes.
  • Documentar quién decide, quién comunica y quién actúa durante un incidente.

Esta lista no es un programa de seguridad completo, pero es un buen inicio. Mueve la conversación del miedo a la responsabilidad.

El LMS sigue siendo central

El incidente del LMS Canvas no hace menos relevantes las plataformas de aprendizaje. Muestra lo contrario.

La educación digital, la capacitación corporativa, el onboarding, el aprendizaje de cumplimiento y la educación continua dependen cada vez más de plataformas estables, auditables y operadas con cuidado. El LMS no es solo un lugar para subir archivos. Es parte de cómo una institución enseña, comunica, evalúa y continúa trabajando.

Esa es la tesis de este artículo: la seguridad LMS no es solo una función de plataforma; es una disciplina operativa.

Para organizaciones que usan el LMS Moodle, o que evalúan un servicio gestionado para el LMS Moodle, Krestomatio puede ayudar a revisar la operación actual, identificar prioridades y entender el costo-beneficio de un enfoque gestionado.

La seguridad no es una campaña de una semana. Es trabajo continuo, responsabilidad clara y mejora constante.

Nota de marca: Moodle y los logotipos asociados de Moodle son marcas comerciales o marcas registradas de Moodle Pty Ltd o sus afiliadas relacionadas. Este artículo usa las marcas de Moodle únicamente para identificar y comentar el LMS Moodle y servicios relacionados, siguiendo la guía de marcas publicada por Moodle. (Moodle Trademark Guidelines)

5 Reasons Your RAG System Will Fail in Production (And the Patterns I Use to Fix Each One)

The 80% Problem

Most RAG demos look magical. You drop in 10 PDFs, ask 3 questions, get clean answers. Ship it.

Then production hits. The document corpus grows from 10 to 10,000. Users ask questions the demo never anticipated. Edge cases stack up. Accuracy drops from 95% to 60% in two weeks. The team starts apologising to the client.

I’ve built 20+ production RAG systems for clients across the USA, UK, UAE, Canada, Australia, Switzerland, and Pakistan. About 80% of the RAG projects I audit before clients hire me are in this exact failure mode — they passed the demo, then collapsed under real data.

The fixes aren’t more complex models. They’re architectural patterns designed for failure modes from day one. Here are the five that matter most.

Failure 1: Hallucinations on edge cases

A vanilla RAG pipeline does this: embed the user query, retrieve top-k documents, stuff them into a prompt, ask the LLM to answer. When retrieval finds something, the LLM dutifully constructs an answer — even when the retrieved context is unrelated to the question.

In production, you get confident-sounding nonsense on the long tail of queries.

The fix: a self-correction loop. Before the LLM answers, force it to grade the retrieved context against the question. If the grade is poor, rewrite the query or fall back to a “I don’t have enough information” response.

from langgraph.graph import StateGraph, END

def grade_relevance(state):
    docs = state["documents"]
    question = state["question"]
    prompt = f"""Given the question and retrieved documents, score 0-10 how
    relevant the documents are to answering the question. Be strict.
    Question: {question}
    Documents: {docs[:3000]}
    Respond with just a number."""
    score = int(llm.invoke(prompt).content.strip())
    return {"relevance_score": score}

def route_after_grading(state):
    if state["relevance_score"] < 6:
        return "rewrite_query"
    return "generate_answer"

graph = StateGraph(RAGState)
graph.add_node("retrieve", retrieve)
graph.add_node("grade", grade_relevance)
graph.add_node("rewrite_query", rewrite_query)
graph.add_node("generate_answer", generate_answer)
graph.add_conditional_edges("grade", route_after_grading)

I built exactly this pattern for an enterprise client — full breakdown in my Agentic RAG case study. It moved accuracy from ~70% to 90%+ on real questions, and dropped hallucinations to single digits.

Failure 2: Stale retrieval as your data changes

You ship a RAG system on Monday with 500 documents. By Friday, 50 of those documents have been edited. Your vector store still has the old embeddings.

Users ask questions about the new content. The system retrieves the old version. They lose trust.

The fix: incremental re-indexing with content hashing, not full re-builds. Hash each source document. On a schedule (or webhook), only re-embed documents whose hash changed.

import hashlib

def document_hash(text, metadata):
    payload = text + str(sorted(metadata.items()))
    return hashlib.sha256(payload.encode()).hexdigest()

def upsert_if_changed(doc_id, text, metadata, pinecone_index):
    new_hash = document_hash(text, metadata)
    existing = pinecone_index.fetch([doc_id]).vectors.get(doc_id)
    if existing and existing.metadata.get("hash") == new_hash:
        return False  # unchanged, skip
    embedding = embed(text)
    pinecone_index.upsert([{
        "id": doc_id,
        "values": embedding,
        "metadata": {**metadata, "hash": new_hash, "indexed_at": now()}
    }])
    return True

This single pattern saved a client 70% on embedding API costs and kept their knowledge base accurate without manual intervention.

Failure 3: Bad retrieval ranking

Top-k retrieval over pure semantic similarity has a known weakness: it rewards documents that sound similar to the question, not documents that answer the question. Worse, exact keyword matches (product codes, names, error codes) often get ranked below conceptually-similar-but-wrong chunks.

The fix: hybrid search + a reranker. Combine dense vector search with sparse keyword search (BM25), then run the merged candidates through a cross-encoder reranker.

from rank_bm25 import BM25Okapi
from sentence_transformers import CrossEncoder

bm25 = BM25Okapi([doc.text.split() for doc in corpus])
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

def hybrid_retrieve(query, k=20):
    dense_hits = vector_store.similarity_search(query, k=k)
    sparse_hits = bm25.get_top_n(query.split(), corpus, n=k)
    candidates = dedupe(dense_hits + sparse_hits)
    pairs = [(query, c.text) for c in candidates]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])
    return [c for c, _ in ranked[:5]]

Why this matters: in financial, legal, and medical use cases, missing a specific code or term means missing the entire answer. Pure semantic search misses these constantly. Hybrid + rerank fixed this for a healthcare client managing 10,000+ patient records.

Failure 4: Multimodal blindspots

Most RAG systems can’t read the charts, diagrams, screenshots, or tables inside PDFs. They OCR the text and lose 40% of the information.

If your domain has visual content — research papers, technical docs, medical scans, financial reports — text-only RAG is broken by design.

The fix: vision-language embeddings (ColPali, CLIP) for image regions alongside text chunks. Index both. Let the retriever match queries against both modalities.

from colpali_engine.models import ColPali, ColPaliProcessor

processor = ColPaliProcessor.from_pretrained("vidore/colpali")
model = ColPali.from_pretrained("vidore/colpali")

def embed_page_image(pdf_page_image):
    inputs = processor(images=[pdf_page_image], return_tensors="pt")
    return model(**inputs).last_hidden_state.mean(dim=1)

# Store both text embeddings AND image embeddings in the same vector store
# with a 'modality' tag. Retrieve from both, then merge.

I built this for a research firm searching 10,000+ pages of mixed-content PDFs. Asking “show me the Q3 conversion funnel chart” actually returns the right chart now. Full writeup: Multimodal RAG with ColPali & CLIP.

Failure 5: No evaluation harness = no improvement

Most teams ship RAG without an evaluation pipeline. Then when accuracy degrades, they can’t tell:

  • Did retrieval get worse?
  • Did the LLM get worse?
  • Did the data get harder?
  • Was it always this bad and we just didn’t notice?

You can’t fix what you can’t measure.

The fix: a golden dataset + automated nightly eval. 50–100 hand-curated question/answer pairs covering your edge cases. Run them through the system every deploy. Track three metrics:

def evaluate_rag(golden_dataset, rag_system):
    results = {
        "retrieval_hit_rate": 0,    # did retrieval find the right doc?
        "answer_correctness": 0,    # did the final answer match?
        "faithfulness": 0,          # was the answer grounded in retrieved docs?
    }
    for q, expected_doc_ids, expected_answer in golden_dataset:
        retrieved = rag_system.retrieve(q)
        answer = rag_system.answer(q)
        results["retrieval_hit_rate"] += any(d.id in expected_doc_ids for d in retrieved)
        results["answer_correctness"] += llm_judge(answer, expected_answer)
        results["faithfulness"] += llm_judge_grounding(answer, retrieved)
    return {k: v / len(golden_dataset) for k, v in results.items()}

This is the single highest-leverage thing you can build. Every RAG improvement I’ve shipped started with one of these metrics moving in the wrong direction.

The Pattern: Design for failure on day 1

If I had to compress all 20 RAG projects into one sentence: the production-ready systems are the ones designed for failure from the first commit. Self-correction loops, hash-based incremental indexing, hybrid retrieval, multimodal embeddings, and an evaluation harness aren’t optimizations you add later — they’re load-bearing infrastructure.

Most “AI demos that broke in production” stories are really “demos without failure handling that met production.” The fix isn’t a smarter model. It’s better architecture.

If you’re building a RAG system that needs to survive real data, look at every component and ask: what happens when this fails? If you don’t have an answer, that’s the next thing to build.

About the Author

I’m Muaz Ashraf, a freelance AI engineer specialising in production-ready RAG systems, AI agents, and AI integration. I’ve shipped 20+ AI systems across 7 countries with a 100% project completion rate.

Open for AI consulting, RAG system development, AI agent development, and LLM application work. Typical MVP delivery: 2–4 weeks.

If you found this useful, follow me here on dev.to — I publish field notes from real production AI work.

Bean Lifecycle in Spring Boot

Dependency Injection in Bean Lifecycle

A polite tour through how Spring creates, uses, and destroys your objects while you pretend you are in control.

Spring Boot applications are mostly made of beans.

A bean is just an object that Spring manages for you.
You write the class. Spring handles the birth, setup, wiring, and death of the object. Like a very organized hotel manager for Java classes. Humanity invented dependency injection because manually creating objects became emotionally exhausting.

Let’s understand the Bean Lifecycle using simple questions and answers.

First Question: What is a Bean?

Suppose we have this service:

@Service
public class EmailService {

    public void sendEmail() {
        System.out.println("Sending email...");
    }
}

@Service tells Spring:

“Please create this object and manage it for me.”

That object becomes a Spring Bean.

Second Question: What does “Lifecycle” mean?

Lifecycle means:

  1. Bean is created
  2. Bean gets dependencies
  3. Bean gets initialized
  4. Bean is used
  5. Bean gets destroyed

Like this:

Create → Inject Dependencies → Initialize → Use → Destroy

Spring does all this automatically.

Because apparently developers once enjoyed writing 200 lines of setup code just to create one object.

Third Question: When does Spring create a Bean?

Spring creates beans when the application starts.

Example:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

When run() happens:

  • Spring scans classes
  • Finds annotations like @Component, @Service
  • Creates objects
  • Stores them inside the Spring Container

The container is basically Spring’s giant object warehouse.

Fourth Question: What happens first in Bean Lifecycle?

Step 1: Bean Instantiation

Spring creates the object.

Example:

@Service
public class UserService {

    public UserService() {
        System.out.println("Constructor called");
    }
}

Output:

Constructor called

This is the very first lifecycle step.

Fifth Question: What happens after object creation?

Step 2: Dependency Injection

Suppose this bean needs another bean.

@Service
public class NotificationService {

    public void notifyUser() {
        System.out.println("Notifying user");
    }
}

Now inject it:

@Service
public class UserService {

    private final NotificationService notificationService;

    public UserService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
}

Spring does this automatically.

It first creates NotificationService, then gives it to UserService.

This is called Dependency Injection.

Fancy name. Simple idea.

Dependency Injection in Bean Lifecycle

Sixth Question: Can we run code after bean creation?

Yes.

Step 3: Initialization

Sometimes we want code to run after dependencies are ready.

Example:

@Service
public class DatabaseService {

    @PostConstruct
    public void init() {
        System.out.println("Database connection initialized");
    }
}

Output:

Database connection initialized

@PostConstruct runs after:

  • bean creation
  • dependency injection

This is useful for:

  • opening connections
  • loading files
  • caching data

Seventh Question: What is the exact order until now?

The order is:

1. Constructor
2. Dependency Injection
3. @PostConstruct

Example:

@Service
public class DemoService {

    public DemoService() {
        System.out.println("1. Constructor");
    }

    @PostConstruct
    public void init() {
        System.out.println("2. PostConstruct");
    }
}

Output:

1. Constructor
2. PostConstruct

Simple. Beautiful. Suspiciously efficient.

Eighth Question: What happens while the application is running?

Step 4: Bean is Ready to Use

Now the bean works normally.

@RestController
public class HelloController {

    private final UserService userService;

    public HelloController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/hello")
    public String hello() {
        return "Hello";
    }
}

Spring keeps the bean alive inside the container.

By default, beans are Singleton.

Meaning:

Only one object is created for the entire application

Ninth Question: What happens when application stops?

Step 5: Bean Destruction

Spring destroys beans gracefully.

We can run cleanup code before destruction.

Example:

@Service
public class FileService {

    @PreDestroy
    public void cleanup() {
        System.out.println("Closing files...");
    }
}

When app shuts down:

Closing files...

Useful for:

  • closing DB connections
  • stopping threads
  • releasing resources

Because memory leaks are the software version of leaving your kitchen gas on.

Tenth Question: What is the Full Lifecycle?

Here is the complete flow:

Spring Starts
     ↓
Bean Created
     ↓
Dependencies Injected
     ↓
@PostConstruct Runs
     ↓
Bean Ready to Use
     ↓
Application Stops
     ↓
@PreDestroy Runs
     ↓
Bean Removed

Full Example

@Service
public class LifeCycleService {

    public LifeCycleService() {
        System.out.println("1. Constructor called");
    }

    @PostConstruct
    public void init() {
        System.out.println("2. Bean initialized");
    }

    public void doWork() {
        System.out.println("3. Bean is working");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("4. Bean destroyed");
    }
}

Possible Output:

1. Constructor called
2. Bean initialized
3. Bean is working
4. Bean destroyed

Final Mental Model

Think of Spring like a factory manager.

You provide class blueprints.

Spring:

  • creates objects
  • connects objects together
  • prepares them
  • manages them
  • destroys them safely

You focus on business logic.

Spring handles object management.

And somewhere deep inside the framework, thousands of reflection calls whisper through the void while your app boots for 14 seconds because someone added three extra starters.

Quick Revision

Step Description
Instantiation Bean object created
Dependency Injection Required beans injected
Initialization @PostConstruct runs
Ready State Bean is used
Destruction @PreDestroy runs

GitHub Copilot Prompts vs Skills vs Instructions: What’s the Difference and When to Use Each

You’ve been using GitHub Copilot for a while. It’s great out of the box, but you keep catching yourself repeating the same context. “We use Vitest, not Jest.”, “Always use server actions, not API routes.”, “Run migrations with this specific command.”

GitHub Copilot now has three primitives that let you codify this kind of knowledge: Instructions, Prompts, and Skills. They overlap just enough to be confusing, but once you see how each one works, the choice becomes obvious.

Let’s break them down.

Instructions: The Always-On Rules

instructions.md are Markdown files that passively shape Copilot’s behavior. Think of them as the coding standards document your team actually reads – because the AI reads it for them.

Where they live:

  • .github/copilot-instructions.md – project-wide rules, loaded on every interaction
  • .github/instructions/*.instructions.md – file-specific or task-specific rules, loaded on-demand

When to use instructions

Use instructions when the guidance applies broadly and should always be in play without anyone explicitly asking for it.

Example 1: Project-wide standards

.github/copilot-instructions.md:

# Project Guidelines

## Code Style
- Use functional components with hooks, never class components
- Prefer named exports over default exports
- Use `pnpm`, not `npm` or `yarn`

## Architecture
- API routes live in `src/app/api/`
- All database access goes through the repository pattern in `src/lib/db/`
- Never import server-only modules in client components

## Testing
- Run tests with `pnpm test`
- Use Vitest, not Jest
- Co-locate test files next to source: `Button.test.tsx` beside `Button.tsx`

This file is loaded into every Copilot interaction. When you ask it to write a test, it’ll reach for Vitest. When it scaffolds a component, it’ll use a named export. No reminders needed.

Example 2: File-specific rules loaded automatically

.github/instructions/react-components.instructions.md:

---
description: "Standards for React components"
applyTo: "src/components/**/*.tsx"
---

# React Component Standards

- Use the `cn()` utility from `@/lib/utils` for conditional classes
- Props types go above the component, named `{ComponentName}Props`
- Use `forwardRef` for any component that wraps a native element

The applyTo glob means this loads automatically whenever Copilot is working on files in src/components/. You never invoke it — it just shows up when relevant.

Example 3: On-demand task instructions

.github/instructions/database-migrations.instructions.md:

---
description: "Use when writing database migrations, schema changes, or data transformations"
---

# Migration Guidelines

- Always create reversible migrations with explicit `up` and `down`
- Never drop a column in the same release as the code removal
- Add indexes concurrently: `CREATE INDEX CONCURRENTLY`
- Test rollback locally before pushing

No applyTo here. Instead, the description tells Copilot when this instruction is relevant. When you ask Copilot to help with a migration, it reads the description, recognizes the match, and pulls in the instructions automatically.

Key Takeaway

Instructions are passive.

They shape behavior without you thinking about them. The best instructions are ones your team writes once and then forgets exist — because Copilot just does the right thing.

Prompts: The Reusable Tasks

What they are: Template files for specific, repeatable tasks you trigger on-demand. They’re like saved chat messages with superpowers — you can pin them to specific models, restrict their tools, and parameterize inputs.

Where they live:

  • .github/prompts/*.prompt.md — workspace-scoped
  • ~/.config/github-copilot/prompts/*.prompt.md — personal, follows you across projects

When to use prompts

Use prompts when you have a single, focused task you do repeatedly and want consistent output every time.

Example 1: Generating tests from code

.github/prompts/generate-tests.prompt.md:

---
description: "Generate test cases for the selected code"
agent: "agent"
---

Generate comprehensive test cases for the provided code:

- Include happy path, edge cases, and error scenarios
- Follow existing test patterns in this codebase
- Use descriptive test names that read like specifications
- Mock external dependencies, never hit real APIs

Now type /generate-tests in chat, and you get consistent, well-structured tests every time. No re-explaining your preferences.

Example 2: Writing a changelog entry

.github/prompts/changelog.prompt.md:

---
description: "Draft a changelog entry from recent git commits"
agent: "agent"
tools: [terminal]
---

Review the git log since the last tag and draft a changelog entry.

Format:
## [version] - YYYY-MM-DD
### Added
### Changed
### Fixed

Group by type. Use past tense. Link PR numbers. Keep entries to one line.

Example 3: PR description generator

.github/prompts/pr-description.prompt.md:

---
description: "Generate a PR description from the current branch diff"
agent: "agent"
tools: [terminal]
---

Generate a pull request description based on the current branch's diff against main.

Include:
1. **Summary** — what changed and why (2-3 sentences)
2. **Changes** — bullet list of notable changes
3. **Testing** — how this was tested
4. **Screenshots** — placeholder if UI changed

Use the conventional commits in the log to understand intent.

Key Takeaway

Prompts are active.

You invoke them deliberately when you want to run a specific task. They’re your saved workflows — small, focused, and reusable. If you’re reaching for the same chat prompt more than twice, it should be a prompt file.

Skills: The Multi-Step Workflows

What they are: Folders containing instructions, scripts, templates, and reference docs that Copilot loads on-demand for complex, multi-step tasks. Skills are the heavy machinery — they bundle everything the agent needs to execute a sophisticated workflow.

Where they live:

  • .github/skills/<skill-name>/SKILL.md — workspace-scoped
  • ~/.copilot/skills/<skill-name>/SKILL.md — personal

When to use skills

Use skills when you need a multi-step workflow with bundled assets — scripts to run, templates to fill, reference docs to consult. If a prompt is a recipe card, a skill is the entire cookbook chapter with ingredients included.

Example 1: A deployment verification skill

.github/skills/verify-deployment/
├── SKILL.md
├── scripts/
│   └── smoke-test.sh
└── references/
    └── endpoints.md

SKILL.md:

---
name: verify-deployment
description: 'Verify a deployment is healthy. Use when deploying, after deploy, smoke test, health check.'
---

# Deployment Verification

## Procedure
1. Ask which environment (staging/production)
2. Run the [smoke test script](./scripts/smoke-test.sh) against the environment
3. Check each endpoint listed in [endpoints reference](./references/endpoints.md)
4. Report results as a table: endpoint, status, response time
5. Flag any endpoint returning non-200 or responding > 2s

This bundles the smoke test script and endpoint list with the instructions. The agent doesn’t need to search for them or ask you where they are.

Example 2: A component scaffolding skill

.github/skills/new-component/
├── SKILL.md
└── assets/
    ├── component.tsx.template
    ├── component.test.tsx.template
    └── component.stories.tsx.template

SKILL.md:

---
name: new-component
description: 'Scaffold a new React component with tests and stories. Use when creating components, new UI element, add component.'
---

# New Component

## Procedure
1. Ask for the component name and directory
2. Create from [component template](./assets/component.tsx.template)
3. Create test from [test template](./assets/component.test.tsx.template)
4. Create story from [stories template](./assets/component.stories.tsx.template)
5. Add barrel export to the directory's `index.ts`

Key Takeaway

Skills are orchestrations.

They package complex workflows with their dependencies. The bar for creating a skill is higher — you need a folder structure, and the task needs to be complex enough to justify it. But for workflows you run regularly, they’re transformative.

Decisions, Decisions, Decisions

The examples above should help

You’ve seen all three, but you’re probably still wondering which option best fits your usecase.

Don’t overthink it — ask yourself these questions:

Question Answer Use
Should this apply without anyone asking? Yes Instructions
Is it a single, focused task? Yes Prompt
Does it need scripts, templates, or multi-step orchestration? Yes Skill
Does it apply to specific file types? Yes Instructions (with applyTo)
Do I want it as a slash command? Yes Prompt or Skill
Is it project-wide context? Yes Instructions (copilot-instructions.md)

Remember:

  • Instructions = “Always do this” (passive)
  • Prompts = “Do this now” (active, simple)
  • Skills = “Do this now, and here’s everything you need” (active, complex)

Getting Started

Don’t try to build all three at once.

  1. Start with copilot-instructions.md — write down the things you keep correcting Copilot on. “Use Vitest.”, “We use pnpm.”, “Components go here.” This takes five minutes and pays off immediately.

  2. Notice your repeated prompts — if you’ve typed “write tests for this using our patterns” more than twice, make it a .prompt.md file.

  3. Graduate to skills when you feel the friction — when a prompt isn’t enough because you need to reference a script or template, that’s your signal to create a skill folder.

The goal isn’t to configure Copilot perfectly on day one. It’s to gradually encode your team’s knowledge so the AI gets smarter about your codebase over time.

Building a 13-Agent AI System for M&A Due Diligence — Architecture Deep Dive

The Problem Nobody Was Solving

As a corp dev lead, I spent weeks doing the same thing after every deal: assembling the cross-domain picture from siloed advisor reports.

Legal would flag a termination clause. Finance would flag revenue concentration. Same entity. Nobody connected the dots.

This happens because due diligence is split into parallel workstreams — legal, financial, commercial, tax, regulatory — each run by separate teams with separate deliverables. The cross-referencing happens in someone’s head, over coffee, two days before the IC memo is due.

The numbers back this up:

  • 31% of M&A failures trace back to DD shortcomings (HBR, McKinsey, KPMG research)
  • DD timelines keep compressing — six weeks becomes three, same scope
  • Corp dev teams screen 200-1,000+ companies/year but close 1-3%

I built Due Diligence Agents to fix this.

What It Does

13 AI agents analyze every document in an M&A data room across 9 specialist domains — Legal, Finance, Commercial, ProductTech, Cybersecurity, HR, Tax, Regulatory, and ESG — then cross-reference findings automatically and trace each one to the exact page, section, and quote.

pip install dd-agents
dd-agents auto-config "Buyer" "Target" --data-room ./your_data_room
dd-agents run deal-config.json

Output: an interactive HTML report, a 14-sheet Excel workbook, and per-subject JSON findings. See a sample report from synthetic data.

The Architecture

The system has four layers:

Layer 1: 38-Step Async Pipeline

The orchestrator (engine.py) is a state machine with 38 async steps grouped into phases:

  1. Setup (steps 1-5): Load config, validate data room, resolve entities
  2. Discovery (steps 6-13): Extract documents, build inventory, classify files, compute precedence
  3. Analysis (steps 14-17): Build specialist prompts, route documents, spawn agents in parallel, check coverage
  4. Cross-Domain (steps 18-20): Symbolic trigger evaluation, targeted respawn, merge
  5. Quality (steps 21-26): Judge review, merge findings, validate, deduplicate
  6. Reporting (steps 27-38): Generate HTML, Excel, JSON, knowledge base

Every step supports checkpoint/resume. If the pipeline crashes at step 23, it restarts from step 23 — not from scratch. Steps are typed, and the state object serializes cleanly to JSON.

Layer 2: 13 Agents

9 specialists + 4 meta-agents, each spawned via Anthropic’s claude-agent-sdk:

Specialists: Legal, Finance, Commercial, ProductTech, Cybersecurity, HR, Tax, Regulatory, ESG. Each gets domain-specific prompts, the relevant documents, and a set of tools (file read, search, finding write).

Meta-agents:

  • Judge: Reviews specialist findings for quality, consistency, and missed coverage
  • Executive Synthesis: Produces the deal-level summary with go/no-go signals
  • Red Flag Scanner: Pattern-matches across all findings for deal-killers
  • Acquirer Intelligence: Tailors findings to the buyer’s strategic context

Specialists run in parallel (batched by resource constraints). Meta-agents run sequentially after all specialists complete.

Layer 3: Neurosymbolic Cross-Domain Analysis

This is the part that solved my original problem.

After specialists produce their findings (pass 1), a deterministic rule engine scans them for cross-domain dependencies. No LLM calls — just Python pattern matching.

# Example: Finance finds revenue recognition issue
# → Rule fires → Legal agent re-examines specific contracts
# for enforceability, clawback clauses, delivery milestones

Seven built-in trigger rules cover the most common M&A cross-domain dependencies:

Source → Target When It Fires
Finance → Legal Revenue recognition finding needs contract enforceability check
Legal → Finance Change-of-control clause needs financial exposure quantification
Legal → Finance Termination-for-convenience needs revenue-at-risk calculation
Legal → ProductTech IP ownership dispute needs technical dependency assessment
ProductTech → Legal Data privacy finding needs DPA/GDPR compliance review
Commercial → Finance SLA risk with >10% service credits needs financial quantification
Finance → Commercial Pricing discrepancy needs commercial rate card validation

When a rule fires, it creates a CrossDomainTrigger with the specific contracts to re-examine and instructions for the target agent. The target agent runs a targeted pass-2 review — only on the cited contracts, not the full data room. This keeps costs bounded.

Budget-capped, priority-ordered. If no triggers fire, zero additional cost.

The design is inspired by the FAOS Platform — asymmetric coupling where symbolic rules constrain the LLM’s scope while the LLM provides judgment. Symbolic decides when intelligence is needed; the LLM provides what to do about it.

Layer 4: 5 Blocking Quality Gates

Every finding goes through validation before it reaches the report:

  1. Coverage gate: Did the agent analyze every assigned document?
  2. Schema validation: Does every finding have the required fields (severity, citations, category)?
  3. Citation verification: Can we trace the finding back to a specific page and quote?
  4. Semantic dedup: Are two agents saying the same thing about the same document? (rapidfuzz token_sort_ratio ≥ 80)
  5. Numerical audit: Do financial figures in findings match what’s in the source documents?

Fail-closed. If validation fails, the pipeline stops — it doesn’t silently produce bad output.

The Chat Mode (My Favorite Feature)

After the pipeline runs, you can interrogate the results:

dd-agents chat --report _dd/forensic-dd/runs/latest

The chat agent has 14 MCP tools: citation verification against source PDFs, cross-contract search, entity resolution, and sandboxed document generation. Ask “build me a board summary of all P0 findings with revenue impact” and it writes a Python script, executes it in a sandbox, and hands you the .xlsx file.

15 Things I Learned Building This

These lessons apply to any system doing cross-document analysis at scale — not just M&A.

1. Extraction is harder than analysis. By a lot.

Everyone focuses on the LLM prompts. But 80% of the real engineering is getting clean text out of messy documents. Our extraction pipeline has 4 tiers: pymupdf → pdftotext → OCR (Tesseract → GLM-OCR) → Claude vision as last resort. Each tier has 6 quality gates (min chars, printable ratio, density, readability, watermark detection, corruption check). Confidence scales with method quality — pymupdf gets 0.9 base, OCR gets 0.65.

2. Entity resolution is your invisible foundation

“IBM”, “International Business Machines”, and “Red Hat” — are these the same entity? We use a 6-stage cascade: exact match → normalized (strip legal suffixes) → alias expansion → fuzzy match (rapidfuzz) → TF-IDF cosine similarity → learned matches from prior runs. Names ≤5 characters are blocked from fuzzy matching — without this, “Inc.” matches random entities.

3. Don’t dump everything into one context. Map-merge-resolve.

A 200-page master agreement might have the deal-killer on page 147. You can’t skip large files. But dumping them into one context drops accuracy from 95% to 74% (Addleshaw Goddard, 510 contracts). Instead: chunk at page boundaries (150K chars, 15% overlap), analyze each chunk independently, merge with priority logic (YES beats NO, specific beats generic), and only invoke LLM arbitration when chunks disagree. The 21-point accuracy gain is entirely engineering — no model change.

4. Hallucination is an engineering problem, not a model problem

No single defense works. We use 5 layers: (1) Pydantic schema validation on every response, (2) mandatory citation with file_path/page/exact_quote verified against source, (3) explicit “NOT_FOUND” escape valve — without this, models fabricate clauses rather than admit ignorance, (4) adversarial Judge review with accusatory framing (“this finding appears fabricated — prove it with a direct quote”), (5) 6-layer deterministic numerical audit.

Layer 3 changed everything. When you tell the model “if you can’t find this clause, say NOT_FOUND,” hallucination drops dramatically.

5. Know when to stop using LLMs

We had an LLM agent doing validation and report synthesis. We replaced it with deterministic Python. Quality went up, cost went down. The rule: use LLMs for analysis and synthesis; use Python for validation, dedup, and audit. If you can write the logic as deterministic code, do it.

6. Self-verification works — but only with accusatory framing

After agents produce findings, a follow-up pass challenges them on high-severity claims. Polite prompts (“please review your finding”) have near-zero effect — models confirm their own output. Accusatory prompts (“this finding appears fabricated,” “the cited clause doesn’t exist”) force re-examination and produce a 9.2% accuracy improvement.

7. Cross-agent dedup is different than you think

When 4 agents analyze the same document, they find the same issue but describe it differently. Three rules: (1) never dedup within the same agent — two similar findings from Legal are intentionally distinct, (2) only dedup across agents on the same document — similar findings on different documents are different findings, (3) keep contributing agent metadata so you know which domains flagged it.

8. Context window engineering is a first-class discipline

It’s not just about fitting data in — it’s about where things go. Critical instructions go at the start (highest recall zone). Document content goes in the middle (lowest recall — ~40% worse). Constraints and format rules go at the end (second-highest recall). We budget 40% of the context window for tool calls and reasoning.

9. Quality gates must be blocking, not advisory

If validation just logs a warning, nobody reads it. If it halts the pipeline, quality is non-negotiable. Same for agent guardrails: hard turn limits (soft at 200, force-kill at 3x), path guards (agents can only write under _dd/), bash guards (24 blocked patterns — no rm -rf, no sudo, no pipe-to-shell). Better to produce nothing than unreliable output.

10. Every claim must be traceable to source

Citation verification uses 4 scopes: exact page match → adjacent pages ±1 → full document fuzzy match (80%+) → cross-file search. That last one matters — if the quote isn’t in the cited file, we search all files for that entity. Auto-corrects file misattribution.

11. Most of what AI finds is noise

Run 9 agents across hundreds of documents and you’ll get thousands of findings. We use a 3-stage classification: noise filter (15 patterns for extraction artifacts), data quality filter (14 patterns for “data unavailable” gaps), then material findings. Plus 5 severity recalibration rules — e.g., a change-of-control clause that only applies to competitors gets downgraded from P0 to P3 automatically.

12. Same clause, different deal, different severity

An anti-assignment clause is P0 in an asset purchase (blocks contract transfer) but P3 in a stock purchase (entity doesn’t change). Deal-type context must flow through the entire pipeline: prompt-time rules, post-hoc deterministic adjustments, and executive judgment overrides — with full audit trail.

13. Every API call is a deal cost

Three model profiles: economy (Haiku for extraction), standard (Sonnet for analysis), premium (Opus for synthesis). Per-agent cost tracking. Hard budget limits that halt the pipeline. Right model for right task.

14. Pydantic v2 everywhere

137+ models with model_json_schema() for structured outputs. Strict mypy across 199 source files. The type system catches real bugs — a finding with evidence instead of citations gets blocked by the schema guard hook before it’s written to disk.

15. Make every run smarter than the last

Inspired by Karpathy’s “LLM Wiki” pattern: a persistent knowledge base compounds across runs. Finding lineage via SHA-256 fingerprinting tracks findings even when wording changes. A NetworkX knowledge graph with 11 typed edge types captures entity relationships, contradictions, and clause interactions. Run 2 knows what Run 1 found — and catches what changed.

Try It

pip install dd-agents
dd-agents auto-config "Buyer" "Target" --data-room ./your_data_room
dd-agents run deal-config.json --dry-run  # Preview without API calls

Sample report (synthetic data, no install needed)

GitHub — Apache 2.0, 3,714 tests, strict mypy.

Built on Anthropic’s Claude Agent SDK. Looking for feedback — especially from anyone who’s dealt with data room analysis and can tell me whether the report structure maps to how DD findings are actually consumed.

Como Organizar um Rack de Rede Corretamente

Figura 01 - Equipamentos que Compõem uma rede

Em muitas empresas e residências, os racks de rede acabam ficando desorganizados com o passar do tempo. Cabos misturados, equipamentos sem identificação e falta de ventilação são problemas comuns que podem causar falhas na rede, superaquecimento e dificuldade na manutenção.

Uma boa organização do rack melhora a estabilidade da rede, facilita futuras manutenções e passa mais profissionalismo para o ambiente.

Neste artigo vou mostrar algumas práticas importantes para organizar um rack de rede corretamente.

O que é um Rack de Rede?

Figura 01 - Equipamentos que Compõem uma rede

O rack é uma estrutura utilizada para armazenar e organizar equipamentos de rede e infraestrutura, como:

Figura 02 - Equipamentos usados para organizar um rack

Ele ajuda na proteção, organização e ventilação dos equipamentos.

Principais Problemas em um Rack Desorganizado

Um rack mal organizado pode causar diversos problemas, como:

  • Aquecimento excessivo
  • Dificuldade na manutenção
  • Cabos danificados
  • Quedas de conexão
  • Falta de identificação
  • Risco elétrico

Além disso, a aparência do ambiente não fica nada profissional.

Como Organizar um Rack Corretamente

1. Organize os Cabos
O primeiro passo é separar corretamente os cabos de energia e os cabos de rede.

Evite deixar fios soltos ou enrolados dentro do rack. Utilize:

Figura 03 - Equipamentos para não deixar fios soltos

Isso melhora a circulação de ar e facilita a identificação dos cabos.

2. Identifique Tudo
Uma das práticas mais importantes é identificar cada cabo e porta da rede.
Você pode utilizar:

Figura 04 - Tipos de Identificadores

Isso reduz muito o tempo de manutenção.

3. Utilize Patch Panel
O patch panel ajuda na organização do cabeamento estruturado.

Além de deixar o rack mais profissional, ele evita danos diretos ao switch durante alterações na rede.

4. Melhore a Ventilação
Equipamentos de rede geram calor constantemente.

Por isso:

Figura 05 - Tipos de Boas Práticas de Ventilação

O superaquecimento pode diminuir a vida útil dos equipamentos.

5. Posicione os Equipamentos Corretamente
Uma boa prática é:

Figura 06 - demostrando a boa prática na organização do rack
Isso melhora a estabilidade e facilita a manutenção.

Benefícios de um Rack Organizado

Um rack bem organizado oferece diversas vantagens:

  • Facilidade na manutenção
  • Melhor ventilação
  • Maior vida útil dos equipamentos
  • Redução de falhas
  • Melhor aparência profissional
  • Maior segurança

Conclusão

A organização de um rack de rede é essencial para qualquer ambiente profissional. Pequenos cuidados com cabeamento, identificação e ventilação fazem grande diferença no desempenho da infraestrutura.

Investir em organização reduz problemas futuros e facilita o trabalho técnico no dia a dia.

Day 27: How Explaining Solana’s Account Model Made Me Finally Understand It

Today’s Challenge Was Different

Day 27 of 100 Days of Solana asked me to do something I’ve never done in a coding challenge before. Put the keyboard down and just explain something.

No code today. Write a DEV article explaining Solana’s account model to a Web2 developer.

I’ve been working through this material for 26 days. I thought I understood it. Writing this post showed me exactly where my understanding had gaps, and filling those gaps was the most useful thing I’ve done all week.

What I Thought I Knew

Coming into Day 27, I could have given you a reasonable summary of accounts:

  • Everything on Solana is an account
  • Accounts have lamports, owner, data, and executable fields
  • Programs and data are separated
  • Rent exemption keeps accounts alive

I knew the vocabulary. I could use it correctly in conversation. But “knowing vocabulary” is not the same as understanding something well enough to explain it to someone who has never heard of Solana.

The First Gap: What “Owner” Actually Means

When I sat down to write the explanation, I started with the owner field. And I immediately hit a wall.

I knew that every account has an owner a program whose address is stored in the owner field. I knew that only the owner can modify the account’s data. But when I tried to explain why this matters and how it works in practice, I realized I’d been holding a fuzzy picture.

Here’s what I had to think through to write clearly about it:

The owner field is not advisory. It’s not a convention. The Solana runtime checks it on every single transaction for every account involved. If your transaction tries to write to an account whose owner doesn’t match your program’s address, the runtime rejects it before it even executes. This isn’t a permission denied error the transaction literally never runs.

Once I understood that the runtime itself enforces ownership, the whole model clicked differently. It’s not access control that your program implements. It’s access control that the network implements, using data your program wrote.

That distinction matters a lot when you’re thinking about security.

The Second Gap: Why Separation of Programs and Data Is Actually Clever

I’ve read “programs and data are separated on Solana” probably fifty times. I understood it as a fact. But explaining why it’s designed this way forced me to think it through.

The first reason is upgradeability, and once I spelled it out, it was obvious: a program that holds no mutable state can be replaced (upgraded) without any data migration. On Ethereum, upgrading a smart contract’s logic while preserving its state is a complex operation that requires proxy patterns and careful migration. On Solana, you just deploy new bytecode to the program account. The data accounts keep working because they’re separate.

The second reason is parallel execution, and this one is less obvious. Solana can run transactions in parallel if they don’t touch the same accounts. By separating program logic (static, in the program account) from program state (dynamic, in data accounts), Solana gives its scheduler the information it needs to identify independent transactions and run them simultaneously.

If programs stored their own state like Ethereum contracts do every transaction touching the same program would be serialized. Separation is what makes parallelism possible at the program level.

I knew Solana was fast. I didn’t fully understand that the account model design is part of why.

The Third Gap: Rent Exemption Is a Deposit, Not a Fee

I’ve been thinking about rent exemption wrong. I thought of it as a recurring cost something you pay to keep an account alive. It isn’t.

Rent exemption is a one-time deposit. You pay it once when you create an account, it sits in the account’s lamports balance, and you get it back in full when you close the account. The “exemption” is from ongoing rent: you pay enough upfront that the account is exempt from ever being charged rent again.

The amount is proportional to how much data the account stores roughly 0.002 SOL per kilobyte. An empty account needs less than 0.001 SOL. A large data account might need 0.01 SOL or more.

When I started thinking of it as a deposit instead of a fee, the UX decisions in Solana apps started making more sense. When a wallet prompts you to “close unused token accounts to recover SOL,” it’s offering to delete those accounts and return the deposits to you. When receiving a new token costs a small amount of SOL, it’s funding the deposit for the new token account.

What Writing Teaches You

The Day 27 challenge is smart. Writing forces a different kind of understanding than coding.

When you code, you can get away with fuzzy understanding. You try things, see what works, copy patterns. The feedback loop is “did it compile and run?” You can write working Solana code without fully understanding why the account model is designed the way it is.

When you write an explanation for another developer, the feedback loop is “does this actually make sense?” You can’t hide behind working code. You have to have a clear model in your head, because if your model is fuzzy, your explanation will be fuzzy, and you’ll know it.

Three things I thought I understood, I actually only half-understood:

  • Owner enforcement (runtime-level, not application-level)
  • Program/data separation (the reason is upgradeability AND parallelism, not just one or the other)
  • Rent exemption (deposit, not fee)

All three gaps closed today. Not from reading documentation. From trying to explain it well.

Tomorrow

Day 28 continues Arc 4. We’re going deeper into the account model I expect program derived addresses (PDAs) are coming. After today, I feel genuinely ready for that instead of just following along.

If you’re on the fence about doing the writing days: do them. They’re the most valuable days in the challenge, precisely because they’re uncomfortable.