DuckDB 1.5.3 Adds Quack Client-Server, SQLite Gets Cypher Graph Extension

DuckDB 1.5.3 Adds Quack Client-Server, SQLite Gets Cypher Graph Extension

Today’s Highlights

This week, DuckDB releases version 1.5.3 with the Quack client-server protocol, enabling concurrent writes for the embedded analytics database. Additionally, the GraphQLite extension brings powerful Cypher-based graph database capabilities directly to SQLite users.

DuckDB 1.5.3: Not an Ordinary Patch Release (DuckDB Blog)

Source: https://duckdb.org/2026/05/20/announcing-duckdb-153.html

DuckDB 1.5.3, despite its “patch release” designation, delivers a substantial update to the popular in-process analytical database. The headline feature is the integration of Quack as a core extension, which fundamentally changes how DuckDB can be deployed. Quack introduces a robust client-server protocol, allowing multiple concurrent writers to interact with a single DuckDB instance. This transforms DuckDB from a purely embedded, single-process database into a more flexible system capable of handling shared data access in distributed environments, while retaining its lightweight, high-performance characteristics.

Beyond Quack, the 1.5.3 release includes numerous other enhancements through its rich extension ecosystem. These likely involve expanded support for various data formats, improved query optimization, and potentially new functions or connectors that bolster DuckDB’s role in data ingestion and transformation workflows. For developers and data engineers, this means a more powerful and adaptable tool for in-process analytics, especially for scenarios requiring fast, local data processing or the creation of portable data applications. The continued evolution of DuckDB’s extension framework, coupled with core performance improvements, reinforces its growing importance in the modern data stack for both interactive analysis and robust data pipeline components. The ability to manage concurrent writes via Quack is a game-changer for shared data access patterns.

Comment: Quack’s promotion to a core extension is huge; it makes DuckDB viable for shared workloads beyond pure embedded analytics, blurring the lines with traditional client-server databases while keeping its speed.

Quack: The DuckDB Client-Server Protocol (DuckDB Blog)

Source: https://duckdb.org/2026/05/12/quack-remote-protocol.html

This blog post introduces Quack, DuckDB’s new client-server protocol, marking a significant evolution for the embedded analytical database. Quack enables multiple DuckDB instances to communicate and share data, effectively allowing a client-server setup. This is a crucial development for use cases where concurrent access and shared state are required, moving beyond DuckDB’s traditional single-process, embedded model. The protocol is designed with simplicity and DuckDB’s performance philosophy in mind, ensuring minimal overhead.

The Quack protocol specifically addresses the need for shared data environments without sacrificing the lightweight nature of DuckDB. It facilitates scenarios where data needs to be accessed and modified by multiple applications or users simultaneously, which was previously a challenge for DuckDB’s embedded architecture. The announcement emphasizes Quack’s simple design, aiming to integrate seamlessly into existing workflows while providing robust capabilities for remote interaction and concurrent writes. This innovation significantly broadens the application scope for DuckDB, making it suitable for more complex data architectures where a scalable, yet agile, analytical database solution is needed. It showcases DuckDB’s commitment to evolving beyond a purely in-process tool while maintaining its core strengths.

Comment: Quack’s approach to client-server for DuckDB is pragmatic; it opens up entirely new use cases for collaboration and shared data without requiring a heavy external server infrastructure.

GraphQLite: An SQLite Extension for Graph Database Capabilities (r/database)

Source: https://reddit.com/r/Database/comments/1tkdqut/need_a_lightweight_graph_visualizer_for/

The news item highlights GraphQLite, an innovative SQLite extension that imbues the popular embedded database with graph database functionalities. By integrating the Cypher query language, GraphQLite allows users to perform complex graph traversals and manipulations directly within their SQLite databases. This development is particularly significant for developers who require lightweight, embedded solutions with advanced data relationship modeling capabilities, without the overhead of a dedicated graph database system.

GraphQLite transforms SQLite into a powerful tool for managing interconnected data, making it suitable for applications like social networks, recommendation engines, or knowledge graphs that benefit from graph-native queries. The ability to use Cypher, a widely recognized graph query language, lowers the barrier to entry for developers familiar with graph databases. This extension exemplifies the versatility of SQLite’s architecture, demonstrating how it can be extended to support specialized data models and query paradigms. For the SQLite ecosystem, GraphQLite represents a crucial step towards expanding its utility beyond traditional relational data, catering to modern data challenges that demand more flexible and expressive data structures.

Comment: An SQLite extension offering full graph database capabilities via Cypher is a game-changer for embedded applications needing complex relationship querying; it brings powerful analytical patterns to the edge.

Custom Copilot Agents: Building Domain-Expert AI Teammates with Skills, MCP Tools, and Custom Knowledge

Most Teams Are Still Using 5% of Copilot

Most developers still treat GitHub Copilot like a very good autocomplete engine. That’s useful, but it’s not the real unlock.

The interesting shift happens when Copilot stops acting like a generic assistant and starts acting like a domain-expert teammate. Instead of re-explaining your deployment rules, your content pipeline, or your release checklist every session, you package that expertise once. Then Copilot shows up already knowing the job.

That’s the difference between using Copilot and building with Copilot. One gives you better suggestions. The other gives you reusable specialists that understand your repo, your patterns, and your operating model.

Want the complete agent manifest, copilot-instructions.md, YAML skill files, and MCP integration code? It’s all in Newsletter Issue #11 → subscribe here

What I Mean by a Custom Copilot Agent

At a high level, a custom Copilot agent is a packaged specialization layer for GitHub Copilot. It gives Copilot a clear identity, focused instructions, and the right tools for a specific domain.

GitHub’s customization story already points in this direction. You can customize Copilot for your project, add knowledge bases, and connect MCP servers to Copilot CLI. I think of a custom agent as the point where those ideas converge into one opinionated package.

The Model Context Protocol gives Copilot a standard way to reach external systems and tools. Your agent design decides which tools matter, which context belongs in memory, and which workflows are worth encoding.

The 3-Layer Architecture

The easiest way to think about custom agent architecture is as three layers working together:

  1. Agent profile layer — the identity declaration.
    This is the small config surface that says: this Copilot specialist owns this domain, responds to these triggers, and should load this knowledge. In practice, that’s an agent manifest in an .agent.md file, often paired with copilot-instructions.md when the runtime needs repository-wide guidance.

  2. Skills layer — the structured prompts.
    This is where repeatable expertise lives. A skill isn’t vague guidance. It’s a reusable procedure: what to check, what to avoid, what sequence to follow, and what “done” looks like. I’ve written before about this in Agent Skills: Microsoft Just Shipped What You’ve Been Building.

  3. Tools layer — the execution boundary.
    This is where the agent gets hands. MCP lets Copilot reach beyond text and interact with real systems. That might mean GitHub workflows, a video pipeline, internal APIs, or a governed task system.

If you’ve read The Three Layers Your AI Agent Is Missing, this should feel familiar. The point is separation of concerns. The agent profile says who the specialist is. Skills say how it should behave. Tools define what it can actually do.

Newsletter subscribers get the full 3-layer custom agent architecture with real TypeScript, configs, and production patterns.

In Issue #11 I share the actual implementation details: the agent manifest pattern, the copilot-instructions.md setup, the YAML skill layout, and the MCP integration shape I use to turn Copilot into domain-specific teammates instead of generic chat sessions.

Two Production Examples

Here are two real patterns from production that made this click for me.

1. A DevOps Copilot Specialist

One custom agent pattern I keep coming back to is a DevOps-focused Copilot specialist.

The domain is narrow but deep: release prep, workflow governance, branch rules, dependency checks, and CI visibility. The agent profile establishes the role, the skills encode the repeatable procedures, and the tools expose the right capabilities to inspect workflows and surface the next action.

That means I don’t start every session with “here are our branch rules, here is how we label releases, here is what counts as a blocker.” Copilot starts there already.

2. A Vidpipe Media Workflow Specialist

The second example is from my media pipeline.

A video workflow has a lot of hidden knowledge: ingestion steps, transcript expectations, caption rules, retry paths, and publishing handoffs.

A custom agent turns that operational knowledge into a reusable asset. The skills explain the workflow stages, the tools expose pipeline state, and the agent profile keeps the agent locked into the right role.

And those two custom agents are only the teaser. The third production pattern in Issue #11 is the meta one: a custom agent that helps me scaffold more custom agents faster.

When to Build a Custom Agent vs. Use Skills Directly

Not every recurring workflow needs a full custom agent.

If the problem is mostly procedural, start with skills. Skills are the cheapest leverage point. They let you capture repeatable know-how without adding a new identity surface or tool boundary.

Build a full custom agent when all three signals show up:

  • you keep repeating the same domain context in session after session
  • the domain needs its own toolset, not just better instructions
  • the work benefits from a clear specialist identity instead of a generic assistant persona

My rule of thumb is simple: if Copilot only needs a better playbook, write a skill. If Copilot needs a job title, a toolkit, and a memory of how your team works, build a custom agent.

That line matters because agent sprawl is real. A skill library can stay lightweight. A custom agent should earn its existence.

Where the Deep Dive Lives

This article is deliberately the overview.

The full agent manifest, copilot-instructions.md, YAML examples, and TypeScript MCP integration is in Issue #11. That’s where I walk through the actual layering, show the production examples in more detail, and explain why this architecture compounds once you have more than one specialist running.

If this topic connects with the rest of your platform work, the next stop after the newsletter is The Agentic Development Blueprint. It connects custom agent architecture to the bigger system: context engineering, guardrails, workflows, and governance.

You should also read the surrounding pieces if you want the bigger picture:

The Bottom Line

The teams getting the biggest lift from Copilot are not the ones asking better one-off questions. They’re the ones turning Copilot into reusable specialists that understand their actual environment.

This was the overview. Newsletter Issue #11 has the step-by-step implementation with real files from 3 production custom agents → Subscribe at htek.dev/newsletter

RTX 5090 Cooling, BeeLlama VRAM Opts, Resizable BAR Performance Gains

RTX 5090 Cooling, BeeLlama VRAM Opts, Resizable BAR Performance Gains

Today’s Highlights

NVIDIA’s upcoming RTX 5090 cooling solutions are detailed, while driver-level optimizations like Resizable BAR deliver significant performance boosts for RTX 5080 users. On the software front, BeeLlama v0.2.0 demonstrates impressive VRAM optimization and inference speedups on the RTX 3090, pushing the boundaries of local LLM performance.

BeeLlama v0.2.0 – DFlash Update Boosts LLM TPS on RTX 3090 (r/LocalLLaMA)

Source: https://reddit.com/r/LocalLLaMA/comments/1tkpz2y/beellama_v020_major_dflash_update_single_rtx_3090/

This update introduces BeeLlama v0.2.0, a significant advancement in local LLM inference, particularly highlighted by its “DFlash” optimization. Running on a single NVIDIA RTX 3090, the new version delivers impressive performance gains for popular open-source models. Benchmarks show Qwen 3.6 27B achieving up to 164 tokens per second (tps), a 4.40x improvement, and Gemma 4 31B reaching 177.8 tps, a 4.93x increase over previous versions. This demonstrates a substantial leap in processing speed for LLM generation.

The core improvement stems from the DFlash update, which likely refers to optimizations in data transfer or memory access patterns within the GPU’s VRAM, crucial for handling large language models efficiently. While prompt processing speed remains near baseline, the focus on generation speed is paramount for interactive LLM applications. This project, available on GitHub, offers a practical tool for developers and enthusiasts looking to maximize their consumer-grade GPU’s potential for high-performance LLM inference, making larger models more viable for local deployment without requiring expensive data center hardware.

The ability to run 27B and 31B parameter models at such high speeds on a single 24GB RTX 3090 is a testament to the continuous innovation in VRAM optimization techniques. These advancements are critical for democratizing access to powerful AI models, allowing researchers and hobbyists to experiment and develop without prohibitive computational costs. The BeeLlama project directly addresses the challenge of fitting and efficiently running large models within the constraints of consumer GPU memory and bandwidth.

Comment: This update showcases how targeted software optimizations can unlock substantial performance gains for LLM inference, making higher-parameter models more accessible on consumer GPUs like the RTX 3090, especially by improving VRAM utilization.

AORUS Details RTX 5090 Infinity GPU Cooling, Targets 77°C Under Load (r/nvidia)

Source: https://reddit.com/r/nvidia/comments/1tkrvwc/aorus_details_rtx_5090_infinity_graphics_card/

AORUS has begun to reveal details about its upcoming flagship graphics card, the RTX 5090 Infinity, offering a glimpse into the cooling solutions designed for NVIDIA’s next-generation GPU. The most notable detail is the claim of maintaining a maximum GPU temperature of 77°C under intense load, specifically during a Furmark stress test. This thermal target is ambitious for a top-tier card, suggesting a highly robust and efficient cooling system will be paramount to handle the anticipated power demands of the RTX 5090.

High-performance cooling solutions are increasingly critical as GPUs push higher power envelopes to deliver leading computational power. Maintaining lower operating temperatures not only ensures sustained boost clocks and prevents thermal throttling, but also contributes to the longevity of the hardware. For enthusiasts and power users, efficient cooling translates directly into greater stability and potential for overclocking, allowing them to extract even more performance from their investment.

This early detail from AORUS provides an important data point on the expected thermal engineering required for the Blackwell architecture. As NVIDIA prepares to launch its 50-series GPUs, focusing on cooling performance underscores the challenges and innovations in balancing raw power with thermal management. It also sets expectations for other AIB partners regarding the cooling capabilities necessary for the RTX 5090, indicating a fierce competition in thermal design for the next generation of high-end graphics cards.

Comment: Achieving 77°C under Furmark is ambitious for a flagship, suggesting a robust cooler design to handle the likely high TDP of the RTX 5090. This sets a strong benchmark for thermal performance in the next generation.

Resizable BAR Delivers Up to 30% Performance Boost for RTX 5080 in Forza Horizon 6 (r/nvidia)

Source: https://reddit.com/r/nvidia/comments/1tkf3ug/forza_horizon_6_resizable_bar_on_vs_off_massive/

A recent report highlights a “massive” performance uplift of up to 30% in Forza Horizon 6 by simply enabling Resizable BAR (ReBAR) on an NVIDIA RTX 5080 graphics card. Resizable BAR is a PCI Express feature that allows the CPU to access the entire GPU frame buffer at once, rather than being limited to 256MB chunks. This can significantly improve data transfer efficiency between the CPU and GPU, leading to performance gains in specific applications and games that can leverage the increased bandwidth.

The “free” nature of this performance boost is particularly noteworthy, as it requires no hardware upgrade beyond ensuring compatibility of the motherboard, CPU, and GPU, and then enabling the feature in the system’s BIOS/UEFI and graphics drivers. While the exact performance gains can vary widely between games, resolutions, and system configurations, a 30% improvement is substantial and makes a compelling case for all compatible users to enable ReBAR.

This finding underscores the importance of optimizing the entire system pipeline, from hardware architecture to driver implementation. NVIDIA, AMD, and Intel have all embraced ReBAR (or Smart Access Memory for AMD), recognizing its potential to unlock latent performance in modern systems. For users with an RTX 5080 and a compatible platform, verifying and enabling Resizable BAR is a straightforward step to enhance gaming performance without additional cost, showcasing how software and firmware optimizations continue to play a crucial role in maximizing GPU output.

Comment: It’s impressive to see such a significant, ‘free’ performance gain from simply enabling Resizable BAR. This underscores the importance of proper system configuration and driver-level optimizations for modern GPUs.

This week in Cursor + .NET — 3 rules + 4 essays (week ending May 22, 2026)

Every weekday a single, opinionated rule for senior C#/.NET engineers using Cursor. Here’s the full week in one read — canonical posts live on the Agentic Architect blog.

New essays this week

Teach Cursor Result<T> instead of throwing

Thu 21 May · C# patterns

Stop the AI from undoing your Result/ErrorOr error model with throw and null on every new prompt.

Seven words that stop Cursor hallucination loops

Wed 20 May · Cursor rules

The correction spiral — and the circuit-breaker rule that ends it before you type the stop phrase.

The Scoped→Singleton DI bug your AI just suggested (and how to catch it)

Tue 19 May · .NET DI

The .NET lifetime bug that ships to production silently — and the one Cursor rule that catches it before merge.

The Context Tax: Why every Cursor session costs you 15 minutes

Mon 18 May · Architecture

The hidden cost senior engineers pay every morning — and a four-rule architecture for eliminating it.

3 daily senior rules

Rule 9: Scoped Capture in Singleton

Fri 22 May

The single most expensive .NET runtime bug: a Singleton holding a Scoped service. Cursor cheerfully writes this without warning. Audit constructor parameters of any class registered as Singleton — if any are typically Scoped (DbContext, repositories, MediatR sender), flag it before merge.

→ Permalink on the blog

Rule 8: CancellationToken Propagation

Thu 21 May

Every async method in your codebase should accept and forward a CancellationToken. Make it a rule that any new async signature without one is flagged. Stops the AI from quietly losing cancellation half-way down a call chain.

→ Permalink on the blog

Rule 2: Persistence Boundaries

Wed 20 May

Never let the AI write database logic in your Controllers. Enforce a strict boundary where all IQueryable access stays in the Infrastructure layer. This keeps your business logic “pure” and readable for the LLM.

→ Permalink on the blog

Try one rule before you trust the whole kit

The free arch-core-lite.mdc is one drop-in Cursor rule that ends the morning re-explanation ritual. Install in 60 seconds, see whether Cursor actually remembers your DI lifetimes, and decide for yourself whether the full kit is worth £19.99.

Canonical home for everything in this digest: https://agenticstandardcontact-byte.github.io/agentic-architect/blog/.

RAG Architecture with n8n + PostgreSQL (pgvector) + Ollama Gemma4 on AWS EC2

Figure 1: Architecture of two AWS EC2, one running gemma4 and the other N8N

This is a submission for the Gemma 4 Challenge: Write About Gemma 4

A Complete Enterprise AI Knowledge Retrieval Architecture for Private Document Intelligence

Summary
This article explains a Retrieval-Augmented Generation (RAG) architecture using n8n, PostgreSQL with pgvector, Ollama, and Gemma4 running on AWS EC2. The platform automatically ingests emails and PDFs, creates embeddings, stores vectors in PostgreSQL, and retrieves contextual information to generate AI answers grounded in enterprise data.

Content
You can view a video of this Gemma4 architecture here:
https://www.youtube.com/watch?v=bTP-sNKlsxc

RAG architectures combine vector search with large language models. In this solution, n8n orchestrates ingestion workflows and query processing. Emails and PDF documents are read automatically, text is extracted and cleaned, then split into semantic chunks. The chunks are embedded using the nomic-embed-text model and stored in PostgreSQL pgvector. When users ask questions, the question is embedded and compared against stored vectors to retrieve the most relevant chunks. Gemma4 then generates a final response using retrieved context.
The architecture uses two AWS EC2 instances. The first server hosts n8n, PostgreSQL, Docker, and orchestration services. The second server hosts Ollama, Gemma4, and the embedding model. This separation improves scalability and isolates AI workloads from orchestration tasks.
Docker containers simplify deployment and maintenance. PostgreSQL with pgvector enables semantic similarity search directly inside the relational database. This architecture is modular and can evolve with future embedding models and LLM technologies.

Business Applications
1.Customer Support AI
Support teams can query manuals, troubleshooting guides, and ticket histories using natural language to accelerate customer assistance.
2.Enterprise Knowledge Management
Organizations can centralize contracts, policies, reports, and procedures into an intelligent AI search platform.
Financial Analytics
Executives can ask natural language questions about sales trends, ERP reports, invoices, and operational metrics.
Technical Details
Infrastructure Requirements:

  • EC2 #1: t3.large or t3.xlarge with PostgreSQL, pgvector, Docker, and n8n.
  • EC2 #2: GPU-enabled instance such as g6e.2xlarge for Ollama and Gemma4.
  • Ubuntu 22.04 recommended.
  • SSD storage and private VPC networking.

Implementation Details:
n8n automates email ingestion, PDF extraction, chunking, embedding generation, and vector storage. Chunk sizes around 1000 characters with overlap improve semantic retrieval. PostgreSQL pgvector performs cosine similarity searches. Gemma4 receives contextual prompts generated from retrieved chunks.
Security and Networking:
Use HTTPS with reverse proxies, encrypted EBS volumes, private networking between EC2 instances, and restricted security groups to protect sensitive enterprise data.
Estimated Costs:

  • EC2 #1 daily cost: approximately USD $2–$3.
  • EC2 #1 monthly cost: approximately USD $60–$90.
  • EC2 #2 GPU server daily cost: approximately USD $25–$40.
  • EC2 #2 monthly cost: approximately USD $750–$1,200.
  • Total monthly infrastructure: approximately USD $850–$1,400 depending on workload.

Figure 1: Architecture of two AWS EC2, one running gemma4 and the other N8N
Figure 1: Architecture of two AWS EC2, one running gemma4 and the other N8N

COMPLETE STEP-BY-STEP FLOW
STEP 1 — User Sends Data Into the System

According to the infographic:

Emails with PDFs are read on EC2 #1.

This is the beginning of the Ingestion Workflow.

The company may receive:

invoices
manuals
contracts
reports
customer emails
technical documentation
support tickets

n8n automatically monitors:

IMAP mailboxes
folders
APIs
SharePoint
Google Drive
CRMs
ERPs
What n8n Does

n8n acts as the automation orchestrator.

Example:

New email arrives
n8n detects it
Downloads PDF attachment
Starts the AI pipeline automatically

No human intervention is required.

*STEP 2 — PDF Text Extraction
*

The infographic shows:

Extract PDF Text

At this stage:

PDFs are parsed
text is extracted
metadata is collected

Metadata may include:

sender
date
filename
document type
department
customer ID
Why This Matters

LLMs cannot directly understand PDFs.

The system must convert documents into raw text before AI processing.

Example:

A 200-page manual becomes machine-readable text.

_STEP 3 — Clean and Normalize Text
_
The infographic shows:

Clean & Normalize Text

Raw PDF extraction is usually messy.

Problems include:

broken lines
duplicated spaces
headers/footers
page numbers
encoding problems
tables split incorrectly

n8n cleans the content using scripts or functions.

Example

Before cleaning:

Invoice #2939

Customer:
ACME Corp

Page 1

After cleaning:

Invoice #2939 Customer: ACME Corp
STEP 4 — Chunking the Text

The infographic shows:

Chunk Text

This is one of the MOST important steps in RAG.

Why Chunking Is Necessary

LLMs have token limits.

A 500-page document cannot be sent entirely to the model.

So the document is split into smaller pieces called:

Chunks

Example chunk size:

1000 characters
200 overlap
What Overlap Means

Suppose chunk #1 ends with:

The warranty expires after…

Chunk #2 begins with:

…after 24 months of operation.

Overlap preserves semantic continuity.

Without overlap:

information can be lost
meaning breaks between chunks
STEP 5 — Create Embeddings

The infographic shows:

Prepare Embedding Payload
Call Embedding Server

This is where semantic AI begins.

What Is an Embedding?

An embedding converts text into mathematical vectors.

The embedding model understands meaning.

Example:

“car”

and

“vehicle”

generate similar vectors.

Embedding Process

Chunk text is sent from EC2 #1 to EC2 #2.

The embedding server uses:

nomic-embed-text

to transform text into vectors.

Example:

[0.023, -0.991, 0.224, …]

These vectors may contain:

768 dimensions
1024 dimensions
1536 dimensions

depending on the model.

Why Embeddings Are Powerful

Traditional search uses keywords.

Embeddings use:

Semantic Meaning

This means users can ask:

How long is the warranty?

Even if the document says:

Coverage remains valid for 24 months.

The system still finds the answer.

_STEP 6 — Store Embeddings in PostgreSQL pgvector
_
The infographic shows:

Store Embeddings in PostgreSQL (pgvector)

Now the vectors are saved in PostgreSQL.

What Is pgvector?

pgvector is an extension for PostgreSQL that adds:

vector storage
similarity search
AI search capabilities

Example table:

id chunk_text embedding
1 warranty info [0.12, …]
Why PostgreSQL Is Used

Advantages:

mature database
ACID compliance
reliability
backups
indexing
SQL support
enterprise-ready

Instead of needing a separate vector DB like Pinecone or Weaviate, pgvector keeps everything inside PostgreSQL.

_STEP 7 — User Asks a Question
_

The infographic says:

User sends question (HTTPS POST /ask)

A user opens the web interface and types:

What is the warranty period for industrial pumps?

The question goes to EC2 #1.

STEP 8 — Create Embedding for the Question

The infographic shows:

Create Embedding for Question

The question itself is transformed into a vector using the SAME embedding model.

This is critical.

If documents and questions use different embedding models:

similarity breaks
retrieval quality drops
*STEP 9 — Vector Similarity Search
*

The infographic shows:

Vector Search in PostgreSQL

This is the core of RAG.

How Similarity Search Works

The question vector is compared against ALL stored chunk vectors.

Using:

cosine similarity
Euclidean distance
inner product

PostgreSQL finds the chunks mathematically closest in meaning.

Example

User asks:

How many vacation days do employees receive?

The database may retrieve:

Employees are entitled to 15 annual leave days.

even without keyword matching.

STEP 10 — Build Context

The infographic shows:

Build Context

The best matching chunks are combined together.

Example:

Chunk 1: vacation policy
Chunk 2: HR policy
Chunk 3: employment handbook

The system assembles them into context.

Why Context Is Critical

LLMs hallucinate when lacking information.

RAG prevents hallucinations by giving:

Relevant Ground Truth Data

The model answers from company knowledge.

_STEP 11 — Build Prompt for Gemma4
_

The infographic shows:

Build Prompt for Gemma4

A structured prompt is generated.

Example:

You are an enterprise assistant.

Answer ONLY using the provided context.

Context:
[retrieved chunks]

Question:
How many vacation days do employees receive?

This prompt engineering layer is extremely important.

*STEP 12 — Send to Gemma4 via Ollama
*

The infographic shows:

Send to Gemma4 (EC2 #2)

The prompt is sent to Ollama.

Ollama exposes APIs like:

/v1/chat/completions

Gemma4 processes:

context
instructions
user question

Then generates the final response.

Why Ollama Is Important

Ollama simplifies:

local LLM serving
model management
GPU usage
API exposure

Without Ollama:

running LLMs locally is much harder.

STEP 13 — Return Answer to User

The infographic ends with:

Answer is returned to the user

The final answer travels back:

Gemma4 → EC2 #1 → Web Client

The user receives a grounded response.

Example:

Employees receive 15 vacation days annually after completing one year of employment.
Why This Architecture Is Powerful

This architecture creates:

Private AI

Data stays inside AWS infrastructure.

Semantic Search

Searches by meaning, not keywords.

Scalable AI

You can scale:

database
workflows
LLM server
embedding server

independently.

Conclusions
This RAG architecture demonstrates how organizations can build private and scalable AI systems using open-source technologies. The combination of n8n, PostgreSQL pgvector, Ollama, and Gemma4 enables intelligent retrieval of enterprise knowledge while maintaining full infrastructure control. The modular design supports future scalability, model upgrades, and advanced AI workflows.

Keep Your Taste

I know I said I was going to keep writing about concepts I use every day as a frontend engineer. Previously, I wrote about things like SPA routing, client-side vs server-side rendering, CDNs and all that good stuff. Life happened, a month went by, and honestly my workflow changed so much that it felt wrong to just jump back in without talking about it first.

Not that I stopped learning or building, far from it actually, but the way I approach features and fixes now is very different from what it used to be, mostly because of AI tools like Claude Code.

These days, I spend more time supervising code than actually typing every line myself.

At first, that feeling messed with me a little bit to be honest. It almost felt like I was cheating the process somehow. Like, if I’m no longer manually implementing every single thing, what exactly is my role now?

But after a lot of reflection, I’m starting to understand that engineering was never just about repetitive implementation. We were primarily created to think. To make decisions. To identify tradeoffs. To know why something should exist and what approach makes the most sense.

One thing AI does brilliantly though is helping ideas move from zero to one very quickly.

Previously, you could have an idea, think deeply about it, maybe even start building it, then life happens. Work happens. Priorities change. Maybe you just lack discipline (sarrry sarrry 😭) and the project just ends up in the dump of unfinished side projects.

I think a lot of engineers can relate to that.

I got an idea a few months ago that was very personal to me. I started actively taking notes during church services and events, but I noticed I rarely went back to read them. Sometimes the next time I opened a note was the next Sunday.

Discipline issue? Maybe. But I started thinking the real problem was visibility. It’s easy to forget things you never see again.

So I built something. A reminder based mobile app that resurfaces my notes intentionally throughout the week.

And building it was the first time I really felt what AI in a workflow actually means.

I had the idea, I knew the problem, AI helped me move fast on implementation. It gave me a solid template to work with. But decent wasn’t enough. My experience with UX meant I could look at what it gave me and immediately know what felt off. Certain flows weren’t natural. Some interactions didn’t match how users actually behave. So I made the adjustments, not because AI did a bad job, but because I already had a standard in my head.

That’s when I really started understanding taste.

And honestly the code AI produces is very decent my bro 😭

Decent enough that if you’re not careful, you stop thinking deeply about the decisions being made. Sometimes I catch myself just accepting changes quickly without really sitting down to understand if it’s actually the best approach.

But a colleague told me something recently that stuck. He said one thing AI will not take away from engineers is taste.

For me, taste is feeding your mind well enough to make sound judgments. Experiencing enough to know what good actually looks like. Because AI can generate templates but taste is what takes things from generic to actually good.

There has to be a standard in your head already, otherwise AI becomes the standard, and that guy can give some very generic stuff 😭

And honestly staying open minded is not a bad thing. It exposes you to a lot. The good stuff, the bad stuff, all of it. And that’s actually how taste gets built. You have to see what doesn’t work to know what does. So when AI puts something in front of you, you already know.

Thinking gives you something to drive towards. Leave your mind completely open and you will accept anything. That’s what taste protects you from.

So yeah, let’s keep building our taste, keep building things, and keep growing into better builders.

Till I come across something again… see you soon. Byeee 👋

I Built chanprobe Because My Go Queues Were Invisible

I like Go channels.

They are one of those language features that feel simple in the best possible way.

You can write something like this:

jobs := make(chan Job, 1024)

go func() {
    for job := range jobs {
        process(job)
    }
}()

And for a lot of cases, that is enough.

Clean, readable, idiomatic.

But after using channels in real services, I kept running into the same uncomfortable problem:

once a channel becomes part of your production pipeline, it also becomes a place where latency can hide.

And native channels do not tell you much.

You can check:

len(jobs)
cap(jobs)

But that is basically it.

That tells you how many items are buffered right now, but it does not answer the questions I usually care about when something is slow.

For example:

Is the producer blocked?
Is the consumer too slow?
How long does an item wait before processing?
Did we drop anything?
When did backpressure start?
Which internal queue is causing the delay?

That is why I started building chanprobe.

Repository:

github.com/devflex-pro/chanprobe

The kind of problem I wanted to solve

Imagine a service that delivers webhooks.

The flow is simple:

HTTP request -> validate -> enrich -> queue -> deliver to customer

Somewhere in the middle, there is usually a channel:

deliveryQueue := make(chan WebhookJob, 10_000)

This works fine until customers start saying:

“Sometimes webhooks arrive 30 seconds late.”

At that point, you start looking around.

CPU looks fine.

Memory looks fine.

The database is not obviously slow.

Logs do not show errors.

The service is not crashing.

But something is still wrong.

One possible explanation is that the delivery workers are slower than the producers. The queue starts filling up. Jobs spend more and more time waiting before a worker picks them up. Eventually, your latency is not in the database or in the network.

It is sitting inside an in-memory channel.

With a normal channel, I can inspect the current length:

fmt.Println(len(deliveryQueue))

But that does not tell me how long the oldest job has been waiting.

And for production debugging, this difference matters a lot.

This is useful:

queue length: 8241 / 10000

But this is much more useful:

oldest item age: 37s

Because now I know that at least one job has already waited 37 seconds before processing.

That is not just a metric.

That is an explanation.

What I wanted the API to feel like

I did not want to build a huge framework.

I also did not want to replace every channel in a Go codebase.

I wanted something explicit that I could use at important async boundaries.

Something like this:

jobs := chanprobe.New[Job]("webhook_delivery", 10_000)

if err := jobs.Send(ctx, job); err != nil {
    return err
}

job, ok := jobs.Recv(ctx)
if !ok {
    return
}

process(job)

The queue has a name, because names matter in observability.

I do not want to know that “some goroutine is blocked on channel send”.

I want to know that webhook_delivery is full, or that email_sender is dropping work, or that image_resize has items waiting for 12 seconds.

Basic usage

Here is a small example:

package main

import (
    "context"
    "fmt"

    "github.com/devflex-pro/chanprobe"
)

func main() {
    ctx := context.Background()

    jobs := chanprobe.New[string]("jobs", 1024)
    defer jobs.Close()

    if err := jobs.Send(ctx, "hello"); err != nil {
        panic(err)
    }

    job, ok := jobs.Recv(ctx)
    if !ok {
        return
    }

    fmt.Println("processed:", job)
}

This is intentionally boring.

The interesting part is not that it can send and receive values.

Channels already do that.

The interesting part is that the queue can describe what is happening inside it.

snapshot := jobs.Snapshot()

fmt.Printf("name: %sn", snapshot.Name)
fmt.Printf("len: %dn", snapshot.Len)
fmt.Printf("cap: %dn", snapshot.Cap)
fmt.Printf("sent: %dn", snapshot.SentTotal)
fmt.Printf("received: %dn", snapshot.ReceivedTotal)
fmt.Printf("dropped: %dn", snapshot.DroppedTotal)
fmt.Printf("oldest item age: %sn", snapshot.OldestItemAge)

In a real service, this gives me a much better starting point during debugging.

Instead of guessing where latency lives, I can ask the queue directly.

Context-aware send and receive

One thing I wanted from the beginning was context support.

With a native channel send, this can block forever:

jobs <- job

Of course, you can write a select manually:

select {
case jobs <- job:
    return nil
case <-ctx.Done():
    return ctx.Err()
}

That is fine, but if every important queue needs the same behavior, I prefer to make it part of the abstraction.

With chanprobe:

if err := jobs.Send(ctx, job); err != nil {
    return err
}

And receiving is similar:

job, ok := jobs.Recv(ctx)
if !ok {
    return
}

For me, this is less about saving a few lines of code and more about making queue behavior consistent across the project.

Drop policies

Not every queue should block forever when it is full.

Sometimes blocking is correct.

For example, if every job must be processed, backpressure should probably propagate to the producer.

Sometimes dropping the newest item is correct.

For example, if the system is overloaded and new work can be rejected.

Sometimes dropping the oldest item is correct.

For example, if you only care about the latest state and old queued values are already stale.

So chanprobe supports different policies.

The default policy is blocking:

jobs := chanprobe.New[Job]("jobs", 1024)

You can also choose DropNewest:

jobs := chanprobe.New[Job](
    "jobs",
    1024,
    chanprobe.WithDropPolicy(chanprobe.DropNewest),
)

Or DropOldest:

jobs := chanprobe.New[Job](
    "latest_events",
    1024,
    chanprobe.WithDropPolicy(chanprobe.DropOldest),
)

The point is not that one policy is better than another.

The point is that queue behavior should be intentional.

If work can be dropped, I want that to be visible.

If producers are blocked, I want that to be visible too.

What the queue can tell you

A snapshot contains things like:

type Snapshot struct {
    Name              string
    Len               int
    Cap               int
    Closed            bool

    SentTotal         uint64
    ReceivedTotal     uint64
    DroppedTotal      uint64

    SendBlockedTotal  uint64
    RecvBlockedTotal  uint64

    SendWaitTotal     time.Duration
    RecvWaitTotal     time.Duration
    ItemWaitTotal     time.Duration

    OldestItemAge     time.Duration
}

The fields I personally care about most are usually not Len and Cap.

They are useful, but they are not enough.

The more interesting fields are:

snapshot.OldestItemAge
snapshot.DroppedTotal
snapshot.SendBlockedTotal
snapshot.SendWaitTotal
snapshot.ItemWaitTotal

Because they explain behavior.

If DroppedTotal is growing, the system is losing work.

If SendBlockedTotal is growing, producers are being slowed down.

If OldestItemAge is high, queue latency is becoming part of user-visible latency.

That is the signal I wanted.

Debugging with expvar

I wanted the core package to stay lightweight.

I did not want to force Prometheus, OpenTelemetry, or any other dependency on users.

So the first built-in exporter is based on expvar.

Example:

package main

import (
    "net/http"

    "github.com/devflex-pro/chanprobe"
)

func main() {
    chanprobe.PublishExpvar("chanprobe", nil)

    http.ListenAndServe(":8080", nil)
}

Then you can inspect:

curl http://localhost:8080/debug/vars

This is not meant to be the final observability story for every production system.

It is just a simple way to expose what the queues know.

Prometheus and OpenTelemetry exporters can live separately without making the core package heavier.

Why not just use pprof or runtime/trace?

I use those tools too.

They are extremely useful.

But I see them as solving a slightly different problem.

pprof and runtime/trace help me understand what the Go runtime is doing.

chanprobe is more application-level.

It is not trying to tell me only that goroutines are blocked.

It is trying to tell me which named queue is responsible.

There is a big practical difference between these two statements:

some goroutines are blocked on channel send

and:

webhook_delivery is 98% full and the oldest item has been waiting for 37s

The second one is much closer to the way I debug real services.

What I intentionally did not build

I deliberately avoided the “clever” version of this project.

There is no unsafe.

There is no runtime monkey-patching.

There is no attempt to resize Go channels magically.

There is no global goroutine scanning.

There is no promise that this is faster than channels.

Actually, it should be obvious: this adds instrumentation, so it has overhead.

That is why I would not use it everywhere.

I would use it only where queue visibility is worth the cost.

For small internal coordination channels, native Go channels are still perfect.

For important queues in production pipelines, I want more information.

What I learned while building it

The hardest part was not making a queue.

The hard part was deciding what behavior should be explicit.

What should happen when the queue is full?

Should send block or fail?

What should happen after close?

Should existing items still be receivable?

What exactly counts as a dropped item?

Which metrics are actually useful, and which ones are just noise?

I also realized that a queue is not just an implementation detail.

In many services, it is part of the system’s behavior.

It can hide latency.

It can create backpressure.

It can drop work.

It can make producers slow.

It can make consumers look fine while users are waiting.

If a queue can affect production behavior, I think it deserves observability.

Current status

chanprobe currently has:

generic bounded queues
context-aware Send and Recv
non-blocking TrySend and TryRecv
drop policies
snapshots
registry
expvar support
examples
tests
benchmarks

The repository is here:

github.com/devflex-pro/chanprobe

It is still small, but already useful enough to try in real Go services.

My next ideas are a Prometheus exporter, better examples, and maybe more detailed latency metrics without making the core package too heavy.

If you have Go services with worker pools, event pipelines, background jobs, or internal queues, I would be curious to hear if this kind of visibility would help you debug production issues faster.

Building a Live Solana TPS Meter with OrbitFlare’s TypeScript SDK

Every Solana dashboard tells you the network’s TPS. None of them tell you how busy the one program you care about is right now. Did Pumpfun cool off after a launch? Is Raydium spiking? Is the program your bot listens to still alive in the last 30 seconds? You’re stuck picking between a smoothed-out aggregate or a paid dashboard that refreshes once a minute.

The smallest thing that answers the question directly is below. Point it at any Solana program, get a live transactions-per-second gauge in the terminal with a recent-signatures tape under it. One TypeScript file, less than 100 LoC, runs in a terminal, built on @orbitflare/sdk.

jetstream tx rate: pumpfun (6EF8rrec...)
chain slot:        421442130

last 1s:     14 tx/s
last 10s:    9.7 tx/s avg

recent signatures:
   0.1s ago  slot 421442129  4bpPQBUHmobNJ4SS9ETLaMwY...
   0.4s ago  slot 421442128  Gsi3P32j6h9Yx3ce1gfHR5Pa...
   0.7s ago  slot 421442128  55AWdcey9N1WTYkmQDXPeKHD...
   ...

Full source: github.com/orbitflare/jetstream-tx-rate.

The honest meter problem

The meter could be built with one Jetstream subscription: filter on the program, count transactions per second. That gets done in 50 lines. The problem only shows up the first time the program goes quiet for ten seconds. The meter reads zero. So does the signature tape. And now there’s no way to know whether the program is actually idle or whether the SDK silently lost the connection three minutes ago.

The fix is one extra subscription on a second transport. WebSocket slotSubscribe() ticks every ~400ms whether anything is happening or not, because the chain itself never stops.

The clients

import { JetstreamClientBuilder } from '@orbitflare/sdk/jetstream';
import { WsClientBuilder } from '@orbitflare/sdk/ws';

const jet = new JetstreamClientBuilder()
  .url('http://jp.jetstream.orbitflare.com')
  .build();

const ws = await new WsClientBuilder()
  .url('ws://ams.rpc.orbitflare.com')
  .build();

That’s it for setup. Jetstream needs no api key. WebSocket picks one up from ORBITFLARE_LICENSE_KEY if set.

The two subscriptions

WebSocket pipes the chain slot into a shared variable. One callback, no state machine:

let currentSlot = 0;
const slotSub = await ws.slotSubscribe();
slotSub.on((s) => {
  if (typeof s?.slot === 'number') currentSlot = s.slot;
});

Jetstream gets the transaction firehose, filtered server-side to just the program of interest. for await drains it and stamps each arrival:

const stream = jet.subscribe({
  transactions: {
    target: {
      accountInclude: [],
      accountExclude: [],
      accountRequired: [program],
    },
  },
  accounts: {},
  ping: { id: 1 },
} as any);

const arrivals: number[] = [];
const recent: { slot: number; sig: string; at: number }[] = [];

for await (const u of stream) {
  const sig = u.transaction?.transaction?.signature;
  if (!sig) continue;
  const now = Date.now();
  arrivals.push(now);
  recent.unshift({ slot: Number(u.transaction!.slot), sig: bs58.encode(sig), at: now });
  if (recent.length > 10) recent.length = 10;
}

The accountRequired filter is the load-bearing piece. It tells the Jetstream server to only ship transactions where the program is in the account list, so the bandwidth that reaches the laptop is already the data that matters. No client-side filtering, no wasted parsing.

Two pieces of state, both trivial. arrivals is a list of millisecond timestamps the renderer reads to compute rates. recent is a 10-element ring of the most recent signatures, kept in arrival order, displayed as a scrolling tape under the gauge.

The renderer

Every second, the renderer does three things. First it drops any timestamp older than 10 seconds from arrivals. Then it counts: how many timestamps in the last 1 second (that’s the instantaneous rate), and the total count divided by 10 (that’s the rolling 10-second average). Finally it clears the screen with an ANSI escape and prints the dashboard.

function render(): void {
  const now = Date.now();
  while (arrivals.length && now - arrivals[0]! > 10_000) arrivals.shift();
  const last1s = arrivals.filter((t) => now - t <= 1_000).length;
  const last10sAvg = arrivals.length / 10;

  process.stdout.write('x1B[2Jx1B[H');
  console.log(`jetstream tx rate: ${label} (${program.slice(0, 8)}...)`);
  console.log(`chain slot:        ${currentSlot}n`);
  console.log(`  last 1s:  ${String(last1s).padStart(5)} tx/s`);
  console.log(`  last 10s: ${last10sAvg.toFixed(1).padStart(5)} tx/s avgn`);
  console.log('recent signatures:');
  for (const r of recent) {
    const age = Math.max(0, (now - r.at) / 1000).toFixed(1);
    console.log(`  ${age.padStart(4)}s ago  slot ${r.slot}  ${r.sig.slice(0, 24)}...`);
  }
}

setInterval(render, 1_000);
render();

Running it

git clone https://github.com/orbitflare/jetstream-tx-rate.git
cd jetstream-tx-rate
npm install
npm start pumpfun       # or raydium, jupiter, or any raw program id

Signatures land within a second of the first run. Watching Pumpfun for five minutes shows it idling around 8-15 tx/s on a slow afternoon and spiking to 50-80 when a launch hits. Raydium runs lighter on average but every big swap is a visible jolt. Any program ID on Solana gets the same view of how busy it actually is, not how busy it averages out.

What this tiny snippet doesn’t have to do

Reconnects, regional failover, api-key scrubbing, ping/pong liveness, the protobuf wire format, the WebSocket re-subscribe dance after a drop. The SDK handles all of it the same way across both transports. None of it shows up in the source. What’s left is a filter, a callback, and the rendering math.

That’s the trade. And a good one. Use the SDK’s plumbing, write the part that’s actually yours.

Resources

  1. Full source: github.com/orbitflare/jetstream-tx-rate
  2. @orbitflare/sdk on npm
  3. SDK docs

Security news weekly round-up – 22nd May 2026

Security lapses, an almost a decade-old vulnerability are among the topics that we’re going to review this week. From all the articles that we’ll review, one thing is common: humans are not perfect and sometimes we need constant reminders to let us know what to do and when to do it.

9-Year-Old Linux Kernel Flaw Enables Root Command Execution on Major Distros

I would like to know the cause behind the surge of Linux vulnerabilities in the past few months. We have had CopyFail, Dirty Frag, Fragnesia, and now this? Does it have anything related to Anthropic’s Mythos? Anything? Let me know in the comments section.

Now, speaking of the article. The title is a good summary of the vulnerability, and the following excerpt tells you more:

The vulnerability, tracked as CVE-2026-46333 (CVSS score: 5.5), is a case of improper privilege management that could permit an unprivileged local user to disclose sensitive files and execute arbitrary commands as root on default installations of several major distributions like Debian, Fedora, and Ubuntu.

Scammers are abusing an internal Microsoft account to send spam links

This type of incident reminds me of the following: always verify everything you read in your email even if it appears to come from a “trusted” source. Imagine seeing an email that appears to be from MSFT and it’s from scammers!

From the article:

This is the latest in a rash of incidents in which hackers or scammers have abused company systems to trick unsuspecting customers in recent months. Earlier this year, hackers broke into a platform used by fintech firm Betterment to send out fraudulent notifications that purported to triple the value of any crypto users send in — a widely known scam used to steal people’s cryptocurrency.

How to Protect Identities and Sessions from Infostealers

Infostealers. I don’t know what to say. This blog from CrowdStrike details the danger they pose to your system, your life, and how to protect yourself.

From the article:

An infostealer is a type of malware specifically designed to do what its name suggests: steal sensitive information. Often deployed through phishing emails, malicious downloads, compromised websites, or exploited vulnerabilities

The impact of an infostealer attack can be devastating. Because infostealers quietly extract sensitive data, organizations often remain unaware until significant damage has been done.

CISA Admin Leaked AWS GovCloud Keys on Github

When I saw the article’s title, I read it all. I kept wondering: how did it happen? I mean, you work for CISA and you put stuff like this in a public GitHub repo and ironically you name it “Private CISA”. This is beyond me.

From the article:

One of the exposed files, titled “importantAWStokens,” included the administrative credentials to three Amazon AWS GovCloud servers. Another file exposed in their public GitHub repository — “AWS-Workspace-Firefox-Passwords.csv” — listed plaintext usernames and passwords for dozens of internal CISA systems.

I honestly believed that it was all fake before analyzing the content deeper. This is indeed the worst leak that I’ve witnessed in my career.

Why geopolitical turmoil is a gift for scammers, and how to stay safe

When something is happening in the world and it’s everywhere like in the news, the papers, e.t.c., you name it. Scammers tend to use such situations to compromise unsuspecting users. That’s why you need to control or should I say keep your emotions “in check” when global events happen. Whether you like what’s going on or not, do not be quick to react when you’re online or offline.

From the article:

A good rule of thumb is never to click on links or open attachments in unsolicited messages, even if they look convincing and appear as if sent from a trusted source.

If you really want to know if it’s a genuine message or not, check independently with the sender; i.e., don’t reply directly or use contact details in the message itself. Or if it’s a news story, go direct to your favored news outlet.

Be cautious of social media accounts, especially those that appear to be customer service accounts for airlines and the like. These are easier than you’d think to set up and platform providers are always a step behind in taking them down.

Credits

Cover photo by Debby Hudson on Unsplash.

That’s it for this week, and I’ll see you next time.

When Stress Disguises Itself as Rational Planning (Bite-size Article)

Introduction

Hello. Today, I want to write a short article about something I recently noticed while working. This is partly a note to myself, and partly a reminder.
Over the past few weeks, I had been feeling a little overwhelmed by everything I needed to do.
Every day, I had the feeling that things were not moving forward the way I wanted. When I looked back on the weekend, I found that many of the things I had expected to finish were still unfinished. But I could not clearly tell what the actual problem was.
I vaguely felt that I needed to change something, so I tried adjusting my task management, changing my schedule, and tweaking different parts of my workflow. But nothing really changed.

Looking Back, I Noticed the Real Problem

So the other day, I decided to take some time and look back at several days that had not gone well.
As I reviewed them, I noticed a common pattern. On those days, I had almost always added something unexpected to my schedule during the day. And the trigger was usually the moment when I felt that I was already slightly behind.
In other words, every time I felt behind, I was unconsciously trying to catch up.

When I think about it now, this was a reaction to stress. But in that moment, I did not feel like I was stressed at all.
“If I make some progress on this today, I can recover a little bit of the delay.”

When I thought that way and acted on it, it felt like a calm and realistic decision.

I did not think stress was driving my judgment. I thought I was simply looking at the situation and adjusting my plan rationally.

Looking back, it seems obvious. But in the moment, I did not notice it at all.

The Loop I Found

As I continued reflecting on it, I started to see a pattern.

I fall a little behind
↓
I think, “I should be able to catch up a little”
  ※ At this point, it feels like a rational decision
↓
The plan becomes unrealistic, so I either cannot finish it or the quality drops
↓
I fall further behind
↓
I feel stressed again

The problem was not just falling behind itself. The real issue was that the stress created after falling behind disguised itself as “rational judgment,” and I kept pushing myself without realizing it.

Why Putting It Into Words Helps

At first, I thought the way to break this loop was to become the kind of person who does not feel stressed when plans fall behind. But that is difficult to do in a short period of time.

What felt more realistic was to pause when I noticed the stress.

More specifically, to put it into words.

  • “Maybe I am feeling stressed because I am behind.”

  • “Is this really something I need to do today, or am I just trying to catch up?”

When you feel overwhelmed by too many tasks, “putting it into words” may not seem directly related to the problem. It does not reduce the number of tasks. It does not erase the delay.
But in this case, the problem was happening at the cognitive level. Stress was mixing into my judgment unconsciously. If that is the case, the intervention also needs to happen at the cognitive level.

Putting the feeling into words is a way to make the stress visible. Once it becomes visible, it becomes easier to notice when I am being pulled into it.

Conclusion

When things are not going well, I quickly start thinking, “I need to change my method.” But this time, I realized that the issue was one step before that.

I thought I was making decisions, but I was actually being moved by stress. Noticing that “I thought I was being rational” may be the turning point.
The next time I feel the same way, I want to first put into words what I am reacting to. That alone may help me act a little differently.

As a side note, the thing that helped me notice this stress was a conversation with Claude. I think the same thing could probably happen by talking to another person.
When I tried to explain the situation I was stuck in, I finally started to see what I had been reacting to. I wrote in this article that putting stress into words makes it visible, but in fact, the reason I started writing this article was that I had already experienced that process.
Depending on the AI tool and how you use it, talking to AI can be a relatively easy option when it feels hard to talk to someone else, or when your thoughts are too messy to organize on your own. If you ever feel similarly overwhelmed, you might try explaining the situation to an AI and checking whether stress has quietly mixed into your judgment or actions.

Thank you for reading.