“Per-Process Controls” (Quantized Executions)

Terms like “per-process controls” are understated in the programming world, and “quantized execution” is the key that makes these terms make sense.

I made a threading architecture that embodies “quantized execution” and “per-process controls” and learned that the key to a productive codebase is having a “surface of execution” that speaks for itself. Each operation is quantized to a unit of arguments (token) and each unit represents a flowing surface of metadata and heuristics.

The architecture I made TokenGate is described best as “an async threading hybrid model with per-process controls”.

In TokenGate there’s two layers of task authority, the first layer is the CPU and IO process tags to control affinity, the second is a direct admin control panel operated on the localhost of the rig running TokenGate.

Example of the control mechanisms in the GUI

Having (per-task or) per-operation commands to change the runtime is helpful, undoubtedly. What I want to discuss is if people would get on board with this if it was simple and practical?

This could be done in a variety of ways and is a huge enhancement to any code base.

As an example; if you had an analysis algorithm for tasks and a flag system, then you could act on this.

In whatever way (there’s plenty) a codebase could add controls to 100% of their systems critical operations. They could monitor their successful returns, predict memory needs, stop processes which are failing, and retain “control” by having a functional “surface of truth”.

This is possible when you “quantize an execution” or when functions are wrapped and decorated as tokens or if you go savvy with some other dynamic means of analysis and task manipulation. Point is, a “unit of execution” that has a “surface of truth” is much more “programmable” than a series of locked and time dependent operations using generic returns/state.

This doesn’t mean all functions are meant to have “controls” but anything which alters or returns data in this perspective should.

Am I the only one who thinks “task controls and analysis” is something that should be more of a conversation?

On a deeper level is “process analysis/mapping” and “ML integrated process handling” topics anyone else is intrigued by?

Also who would be on board with a new paradigm of task execution and analysis like this?

Anthropic Proved AI Can’t Evaluate Its Own Work. Here’s How I Rebuilt My Claude Code Setup Around That.

I’ve been building products with Claude Code for months. Every time I asked “is this implementation correct?”, the answer was “yes, it’s properly implemented.” Every time. Even when the code had bugs that broke in production.

Then Anthropic published a blog post that explained exactly why. I mapped my setup against their findings, and realized: my evaluator layer was almost empty.

Here’s how I rebuilt it.

Jump to:

  • What Anthropic’s experiment showed
  • Mapping this to Claude Code
  • Layer 1: Rules — always-on review criteria
  • Layer 2: Skills — on-demand reviewers
  • Layer 3: Agent separation — who builds vs who reviews
  • 3 principles for evaluation design
  • Final file structure
  • Harness design checklist

What Anthropic’s experiment showed

In March 2026, Anthropic published “Harness design for long-running apps” — experiments where AI agents autonomously built apps over multi-hour sessions.

The headline finding:

Agents asked to evaluate their own work tend to confidently praise it, even when it’s clearly mediocre to human observers.

It gets worse. Agents would spot real problems, then wave them off as unimportant and approve anyway. They’d skim instead of testing edge cases. Anthropic themselves put it bluntly: “Claude is an inadequate QA agent out of the box.”

Their fix was splitting generation from evaluation — three agents:

Agent Role
Planner Expands a one-line prompt into a full product spec
Generator Writes the code
Evaluator Clicks through the running app, finds bugs, scores against criteria

The difference was stark:

Setup Time Cost Result
Solo (no evaluator) 20 min $9 Core features broken
Full harness (with evaluator) 6 hrs $200 Basic functionality worked + AI features

The evaluator checked 27 criteria per sprint and filed bug reports like “fillRectangle exists but doesn’t fire on mouseUp.”

Reading this, it clicked: Claude Code’s config system can give you the same split.

Mapping to Claude Code

Here’s the mapping:

Anthropic’s agent Claude Code equivalent What goes here
Planner CLAUDE.md + planning skills Project context, constraints, design rationale
Generator Claude Code + technical skills Code generation
Evaluator Review agents + rules + hooks Quality gates, automated checks

When I laid out my own setup this way, the gap was obvious. CLAUDE.md had project context. Skills had coding patterns. But nothing was actually checking whether the output was correct. I was asking the generator to review its own work — the exact failure mode Anthropic documented.

Layer 1: Rules — always-on review criteria

Files in ~/.claude/rules/ load every session. Put things here that AI won’t do on its own but that matter in production.

Here’s a taste of my Supabase/PostgreSQL rules (30 total):

# ~/.claude/rules/supabase-postgres.md

## Index FK columns
PostgreSQL does NOT auto-index foreign key columns.

## RLS performance
Wrap auth.uid() in SELECT to prevent per-row execution:
- BAD: using (user_id = auth.uid())
- GOOD: using (user_id = (select auth.uid()))

## Cursor-based pagination
- BAD: OFFSET (slow on deep pages)
- GOOD: WHERE id > $last_id ORDER BY id LIMIT 20

Without this rule, the AI generates auth.uid() without the SELECT wrapper every time. Works fine with small tables. Tests pass. Then production slows to a crawl as rows grow. Classic “surface-level test that misses the deeper bug” — exactly what Anthropic described.

I think of rules as “never step on this mine again” files. Every rule started as a real mistake.

Layer 2: Skills — on-demand reviewers

Rules load every session, so too many will eat your context window. Anything task-specific goes into skills (.claude/skills/), which show only their titles by default and load on demand.

You can wire up keyword-triggered activation:

# ~/.claude/CLAUDE.md (excerpt)

## Automatic Skill Activation

### Testing / TDD
**Trigger:** test, TDD, coverage, unit test
**Action:** Run test-driven-development skill

### Bug / Error handling
**Trigger:** bug, error, debug, broken
**Action:** Run systematic-debugging skill

### Completion check
**Trigger:** done, verify, review, check
**Action:** Run verification-before-completion skill

The completion check is the big one. In Anthropic’s experiment, the evaluator ran checks at the end of each sprint. Same idea here — say “verify this” and a quality checklist kicks in behind the scenes.

I run 27 skills total. 7 auto-activate on keywords.

Layer 3: Agent separation — who builds vs who reviews

The core insight from Anthropic: separate the builder from the reviewer. In Claude Code, you do this through agent orchestration:

# ~/.claude/rules/agents.md

## Role: Manager
You are a manager and agent orchestrator.

**Rules:**
- Never implement directly — delegate all implementation to Sub Agents
- Break tasks into small units and run PDCA cycles

## Delegation
### Always delegate:
1. Code implementation
2. Debugging
3. Test creation

### Manager handles directly:
1. Task decomposition
2. Progress verification
3. Plan adjustment

The trick: pin the main Claude as “manager = reviewer” and push all implementation to Sub Agents. This gives Claude Code the same generator/evaluator split that Anthropic proved works.

Main Claude doesn’t write code. It reviews what Sub Agents produce. Planner + evaluator = main Claude. Generator = Sub Agents.

3 principles for evaluation design

1. Weight your criteria toward what AI misses

Anthropic’s frontend experiment used 4 criteria: design quality, originality, technical execution, functionality. They weighted design and originality higher — because the AI already did well on technical execution and functionality.

Lower the weight on things AI handles naturally. Raise it on things AI overlooks.

In practice:

  • Low weight (AI’s fine): syntactically correct code, basic API endpoints
  • High weight (AI misses): performance traps (auth.uid() without SELECT), UX decisions that affect conversion, security blind spots

2. Prune your harness as models improve

From Anthropic:

Every component of a harness encodes an assumption about what the model can’t do alone, and these assumptions can be wrong or quickly become outdated as models improve.

When they went from Opus 4.5 to 4.6, sprint decomposition became unnecessary — the newer model handled long sessions on its own. Same thing happens with your Claude Code rules. Something essential today may be dead weight next quarter.

I’ve revised my CLAUDE.md 8 times. The test for each line: “If I delete this, will the AI make mistakes?” No? Cut it.

3. Better models mean more harness possibilities, not fewer

The space of interesting harness combinations expands rather than contracts as models improve.

Sounds backwards, but it matches my experience. As the AI gets smarter, you can delegate more. But the remaining evaluation points get more nuanced, more subtle, and higher-stakes. The easy rules go away. The hard judgment calls stay.

Final file structure

~/.claude/
├── CLAUDE.md                # Planner layer (project overview + skill triggers)
├── rules/
│   ├── agents.md            # Generator/evaluator split
│   ├── supabase-postgres.md # Review criteria (DB/RLS, 30 rules)
│   ├── react-nextjs.md      # Review criteria (React/Next.js)
│   ├── security.md          # Review criteria (security)
│   ├── coding-style.md      # Code quality
│   └── testing.md           # Test quality
└── skills/                   # On-demand reviewers
    ├── verification-before-completion  # End-of-task checks
    ├── systematic-debugging            # Debug-time checks
    ├── test-driven-development         # TDD enforcement
    └── ... (27 total)

How it maps to Anthropic’s architecture:

  • CLAUDE.md → Planner artifact (the product spec)
  • rules/ → Review criteria (the sprint contract)
  • skills/ → Specialist reviewers (activate when needed)
  • agents.md → Builder/reviewer separation

Harness design checklist

Planner layer (CLAUDE.md)

  • [ ] Project overview and constraints documented
  • [ ] Design decisions and their rationale included
  • [ ] Under 200 lines (bloated CLAUDE.md degrades instruction-following)

Review criteria (rules/)

  • [ ] Cover what AI misses, not what it already does well
  • [ ] Weighted toward production-critical concerns
  • [ ] Regularly pruned as models improve

On-demand reviewers (skills/)

  • [ ] Separated by domain (DB, React, security, etc.)
  • [ ] Keyword auto-activation configured
  • [ ] Completion verification skill exists

Builder/reviewer separation (agents.md)

  • [ ] Main Claude delegates implementation to Sub Agents
  • [ ] Verification flow for implementation results

Links

Pointers in C — Complete Guide

Pointers are one of the most powerful and fundamental features in the C programming language. If you truly understand pointers, you unlock low-level memory control, efficient data structures, and system-level programming.

1. What is a Pointer?

A pointer is a variable that stores the memory address of another variable.

int a = 10;
int *p = &a;

Memory Diagram

Stack Memory:

+------------+        +------------+
| a = 10     | 0x100  | p = 0x100  | 0x200
+------------+        +------------+

p ───────────────► a

Debug Scenario

  • ❌ Bug: printing pointer incorrectly
printf("%d", p); // WRONG
  • ✅ Fix:
printf("%p", (void*)p);

2. Pointer Declaration

data_type *pointer_name;

Memory Insight

The pointer size is fixed (typically 4 or 8 bytes), regardless of type.

int *p;   // 8 bytes (on 64-bit system)
char *c;  // 8 bytes

Debug Scenario

  • ❌ Misinterpretation bug
char *p;
int x = 1000;
p = (char*)&x;
printf("%d", *p); // unexpected value

3. Address Operator (&)

int x = 5;
printf("%p", &x);

Memory Diagram

+------------+
| x = 5      | 0x300
+------------+

&x = 0x300

4. Dereference Operator (*)

int x = 5;
int *p = &x;
printf("%d", *p);

Memory Flow

p (0x400) ───► 0x300 ───► value 5

Debug Scenario (Segmentation Fault)

int *p = NULL;
printf("%d", *p); // CRASH

✔ Cause: dereferencing NULL

5. Pointer Initialization

Memory Risk

int *p;  // contains garbage address like 0xDEADBEEF

Debug Scenario (Crash)

int *p;
*p = 10; // undefined behavior

✔ Fix:

int *p = NULL;

6. NULL Pointer

int *p = NULL;

Memory Representation

p = 0x0

Safe Usage Pattern

if (p != NULL) {
    *p = 10;
}

7. Pointer Arithmetic

int arr[3] = {10, 20, 30};
int *p = arr;
p++;

Memory Diagram

Address:   0x100   0x104   0x108
Values:    10      20      30
             ▲
             p (after p++)

Debug Scenario (Out of Bounds)

int arr[3] = {1,2,3};
int *p = arr;
p += 5;
printf("%d", *p); // undefined

8. Pointers and Arrays

int arr[3] = {1,2,3};
int *p = arr;

Internal Model

arr == &arr[0]
p + 1 == &arr[1]

Debug Scenario

printf("%lu", sizeof(arr)); // 12
printf("%lu", sizeof(p));   // 8

✔ Arrays ≠ pointers

9. Pointer to Pointer

int x = 5;
int *p = &x;
int **pp = &p;

Memory Diagram

pp ──► p ──► x
0x500  0x400  0x300

10. Function Pointers

int add(int a, int b) { return a + b; }
int (*fp)(int,int) = add;

Memory Model

Code Segment:
add() at 0x900

Stack:
fp = 0x900

Debug Scenario

int (*fp)(int,int);
fp(2,3); // crash

✔ Fix: initialize before use

11. Dynamic Memory Allocation

int *p = malloc(sizeof(int));
*p = 10;
free(p);

Memory Diagram

Heap:
0xA00 → [10]

Stack:
p = 0xA00

Debug Scenario (Memory Leak)

int *p = malloc(sizeof(int));
p = NULL; // lost reference

✔ Leak: memory still allocated

12. Void Pointer

void *p;

Rule

Cannot dereference without casting

int x = 5;
p = &x;
printf("%d", *(int*)p);

13. Dangling Pointer

int *p = malloc(sizeof(int));
free(p);

Memory Diagram

p ──► freed memory (invalid)

Debug Scenario

*p = 20; // undefined behavior

✔ Fix:

free(p);
p = NULL;

14. Wild Pointer

int *p;

Memory

p = random address

Debug Scenario

*p = 10; // crash

15. Const Pointer

const int *p;
int *const p;

Memory Behavior

  • const int *p → value locked
  • int *const p → address locked

16. Pointer and Strings

char *str = "Hello";

Memory Layout

Code Segment:
"Hello" (read-only)

Stack:
str → address of string

Debug Scenario

str[0] = 'h'; // crash (read-only memory)

17. Pointers vs Arrays

Deep Difference

int arr[3]; // fixed memory
int *p;     // dynamic reference

Debug Scenario

arr = p; // ❌ illegal

18. Memory Layout (Deep View)

+-------------------+
| Code Segment      |
+-------------------+
| Global/Static     |
+-------------------+
| Stack             |
|  - local vars     |
+-------------------+
| Heap              |
|  - malloc memory  |
+-------------------+

Debug Scenario (Stack Overflow)

void func() {
    int arr[1000000];
}

19. Common Mistakes (With Debug Insight)

  • Uninitialized pointer → crash
  • Double free → heap corruption
  • Memory leak → performance issue
  • Out-of-bounds → undefined behavior

20. Best Practices (Professional Level)

  • Always initialize pointers
  • Use NULL after free
  • Use tools like:

    • valgrind
    • address sanitizer

🚀 5 Practical Projects Using Pointers (Advanced Level)

1. Dynamic Array (Vector)

Add:

  • resizing logic
  • capacity vs size tracking

2. Linked List (Singly)

Add:

  • insert/delete at position
  • reverse list (pointer manipulation heavy)

3. String Library (Pointer-only)

Implement using only pointers (no indexing)

4. Custom Memory Allocator

Simulate heap blocks:

[metadata][data][metadata][data]

5. Callback/Event System

Simulate event handlers using function pointers

Conclusion

Pointers are not just a feature — they are the core of C programming.

With memory diagrams + debugging understanding, you now think like a systems programmer.

What was your win this week!?

From the film Ferris Bueller

👋👋👋👋

Looking back on your week — what was something you’re proud of?

All wins count — big or small 🎉

Examples of ‘wins’ include:

  • Getting a promotion!
  • Starting a new project
  • Fixing a tricky bug
  • Taking a well deserved day off! 💤💤💤

From the film Ferris Bueller's Day Off: three friends ride in a red convertible with the top down on a sunny day.

Happy Friday!

Why bcrypt Is Not Enough in 2026 And What We Built Instead

The Story Behind This Package

Every time I started a new Node.js backend project, I found myself doing the same thing.

npm install bcrypt
npm install joi
# copy-paste AppError class from some tutorial

Three packages. Three different docs. Three different ways of doing the same things. And I did this in every single project — and so did every developer I knew.

Me and my friend Ubed decided to fix this. We built nodearmor —a single npm package that replaces all of that with one install.

npm install nodearmor

This is the story of why we built it, what problem each module solves, and what we learned publishing our first open source npm package.

The Problem With bcrypt in 2026

Let me start with the biggest one — password hashing.

bcrypt has been the default answer to “how do I hash passwords in Node.js” for over 15 years. Search that question right now. bcrypt is in the first three results. Every tutorial uses it. Every project copy-pastes it.

Here is the problem nobody talks about. bcrypt was designed in 1999.

In 1999, hashing was done on CPUs. CPUs had one or two cores. An attacker trying to crack a stolen database of bcrypt hashes could try maybe 100 passwords per second. At that speed, cracking a strong password would take years.

In 2025, attackers use GPUs. A modern GPU has 4,000 to 10,000 cores. Every core can attempt a bcrypt hash simultaneously. That 100 attempts per second becomes 400,000 attempts per second. The slowness that bcrypt provides is completely parallelized away.

bcrypt is not broken. But it is not the best we can do in 2025.

The Solution — Argon2id

Argon2id won the international Password Hashing Competition in 2015 — a competition run by cryptography researchers specifically to find the best password hashing algorithm for the modern era.

The key difference is that Argon2id is memory-hard.

Every single hash attempt requires a large, fixed amount of RAM — 64MB by default. You cannot skip the RAM requirement. The algorithm is mathematically designed so that without the full 64MB, the computation cannot complete.

Here is the math that matters:

bcrypt attack in 2025:
GPU with 4,000 cores — 400,000 parallel attacks per second

Argon2id attack in 2025:
GPU with 16,000 MB of RAM
16,000 MB ÷ 64 MB per attempt = 250 parallel attacks maximum

The attacker with Argon2id needs 16 times more GPUs to achieve the same attack speed they had with bcrypt. GPUs cost money. Electricity costs money. The attack becomes economically impractical.

OWASP — the Open Web Application Security Project — updated their recommendation in 2025. Argon2id is their first choice for password hashing. bcrypt is listed as acceptable only if Argon2id is not available.

What nodearmor Does

nodearmor bundles four modules that every serious Node.js backend needs:

Module Replaces What It Does
nodearmor/env dotenv + manual checks Type-safe env validation at startup
nodearmor/hash bcrypt Argon2id password hashing
nodearmor/guard joi / express-validator Zod-powered request validation
nodearmor/errors copy-pasted AppError Typed HTTP error classes

Let me walk through each one.

Module 1 — env

The Problem

What happens when your app starts without a DATABASE_URL?

It starts fine. No errors at boot time. Then 10 minutes later, when it tries to connect to the database, it crashes — with an error that points to a database file, not the missing environment variable. You spend 20 minutes debugging something that should have been caught immediately.

The Solution

nodearmor/env validates every environment variable at startup. If anything is missing or wrong, the process exits immediately before the server starts — with a clear message listing every problem at once.

import { envault } from "nodearmor/env";

export const env = envault({
  DATABASE_URL: { type: "string", message: "Set DATABASE_URL to your PostgreSQL connection string" },
  PORT:         { type: "number", default: 3000, min: 1000, max: 65535 },
  DEBUG:        { type: "boolean", default: false },
  NODE_ENV:     { type: "string", enum: ["development", "production", "test"] },
  API_BASE_URL: { type: "url" },
  ADMIN_EMAIL:  { type: "email" },
  STRIPE_KEY:   { type: "string", required: false },
});

// All values are correctly typed — no casting needed anywhere
env.PORT        // number
env.DEBUG       // boolean
env.NODE_ENV    // string
env.STRIPE_KEY  // string | undefined

When validation fails, you see this — all problems at once:

nodearmor/env — validation failed:

  x  Missing required variable: "DATABASE_URL"
  x  "PORT" must be >= 1000, got: 80
  x  "NODE_ENV" must be one of [development, production, test], got: "staging"

Fix everything. Restart once. No more guessing.

What We Overcame

Before envault, developers wrote this in every project:

if (!process.env.DATABASE_URL) {
  console.error("DATABASE_URL is required");
  process.exit(1);
}

const PORT = parseInt(process.env.PORT || "3000");
if (isNaN(PORT)) {
  console.error("PORT must be a number");
  process.exit(1);
}

15 lines of boilerplate that grows with every new variable. No TypeScript types. No defaults. No format validation. envault replaces all of it with a clean schema definition.

Module 2 — hash

The Problem

bcrypt — already covered above. CPU-only, 1999, vulnerable to modern GPU attacks.

The Solution

nodearmor/hash wraps Argon2id with OWASP 2025 recommended defaults baked in. The API is almost identical to bcrypt.

import { hash, verify, needsRehash } from "nodearmor/hash";

// REGISTER — hash before storing in database
const passwordHash = await hash(plainPassword);
// Returns: "$argon2id$v=19$m=65536,t=3,p=1$..."

// LOGIN — verify against stored hash
const isValid = await verify(passwordHash, plainPassword);
// Returns: true or false

// MAINTENANCE — upgrade old hashes automatically
if (await needsRehash(passwordHash)) {
  const newHash = await hash(plainPassword);
  await db.users.updateHash(userId, newHash);
}

Notice the hash string that comes back — $argon2id$v=19$m=65536,t=3,p=1$.... It is self-describing. The algorithm name, version, all parameters, and the random salt are all embedded in the string. You never need to store the salt separately. You never need to remember what parameters you used.

Migrating From bcrypt

You do not need to force users to reset their passwords. The migration is transparent — users upgrade automatically on their next successful login.

import bcrypt from "bcrypt";
import { hash, verify } from "nodearmor/hash";

async function login(email: string, plainPassword: string) {
  const user = await db.users.findByEmail(email);

  let isValid = false;

  if (user.passwordHash.startsWith("$2b$")) {
    // old bcrypt hash — verify with bcrypt
    isValid = await bcrypt.compare(plainPassword, user.passwordHash);
    if (isValid) {
      // silently upgrade to Argon2id on next login
      await db.users.updateHash(user.id, await hash(plainPassword));
    }
  } else {
    // new Argon2id hash
    isValid = await verify(user.passwordHash, plainPassword);
  }

  if (!isValid) throw new Unauthorized("Invalid credentials");
  return issueToken(user);
}

Module 3 — guard

The Problem

Without validation middleware, every developer writes this in every route:

app.post("/register", async (req, res) => {
  if (!req.body.email || !req.body.email.includes("@")) {
    return res.status(400).json({ error: "Invalid email" });
  }
  if (!req.body.password || req.body.password.length < 8) {
    return res.status(400).json({ error: "Password too short" });
  }
  // ... 10 more lines of manual checks
  // ... finally the actual route logic
});

10 to 15 lines of boilerplate. No TypeScript types on req.body. Copy-pasted into every route. Different format in every project. Frontend developers do not know what error shape to expect.

The Solution

nodearmor/guard takes a Zod schema and returns an Express middleware. One line. Validation runs before your route handler. req.body is fully typed.

import { guard } from "nodearmor/guard";
import { z }     from "nodearmor";

const RegisterSchema = z.object({
  email:    z.string().email(),
  password: z.string().min(8),
  name:     z.string().min(1).max(100),
});

app.post("/register",
  guard(RegisterSchema),       // one line — validation done
  async (req, res) => {
    const { email, password, name } = req.body; // fully typed
    // no validation code here — it already ran
  }
);

When validation fails, the response is always the same structure:

{
  "status": 400,
  "code": "VALIDATION_FAILED",
  "message": "Request validation failed",
  "issues": [
    { "field": "email",    "message": "Invalid email",     "code": "invalid_string" },
    { "field": "password", "message": "String must contain at least 8 character(s)", "code": "too_small" }
  ]
}

Same shape. Every route. Every project. Your frontend writes one error handler and it works everywhere.

You can also validate query parameters and URL parameters:

// Validate query params — GET /users?page=2&limit=50
const PaginationSchema = z.object({
  page:  z.coerce.number().min(1).default(1),
  limit: z.coerce.number().min(1).max(100).default(20),
});

app.get("/users", guard(PaginationSchema, "query"), async (req, res) => {
  const { page, limit } = req.query; // real numbers, not strings
});

// Validate URL params — GET /users/:id
const IdSchema = z.object({
  id: z.string().uuid("User ID must be a valid UUID"),
});

app.get("/users/:id", guard(IdSchema, "params"), async (req, res) => {
  const { id } = req.params; // guaranteed to be a valid UUID
});

Module 4 — errors

The Problem

Search any Node.js project on GitHub. You will find one of these patterns:

// Pattern 1 — magic numbers everywhere
return res.status(404).json({ error: "User not found" });

// Pattern 2 — copy-pasted AppError class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode; // sometimes 'status', sometimes 'statusCode'
  }
}

// Pattern 3 — everyone writes it differently
throw new AppError("User not found", 404); // no code field, no meta, no types

Every project looks different. Frontend developers write defensive code trying to handle every possible error shape.

The Solution

nodearmor/errors provides typed HTTP error classes that always produce the same JSON structure.

import {
  NotFound, Conflict, Unauthorized,
  Forbidden, TooManyRequests,
  isApiError, toResponse,
} from "nodearmor/errors";

// In your routes — one line, fully typed
throw new NotFound("User not found", { userId: req.params.id });
throw new Conflict("Email already registered", { field: "email" });
throw new Unauthorized("Token expired or invalid");
throw new Forbidden("Admin access required");
throw new TooManyRequests("Rate limit exceeded", { retryAfter: 60 });

Every error serializes to the same shape:

{
  "status":  409,
  "code":    "CONFLICT",
  "message": "Email already registered",
  "meta":    { "field": "email" }
}

Write one error handler for your entire app:

import { isApiError, toResponse } from "nodearmor/errors";

app.use((err, req, res, next) => {
  if (isApiError(err)) {
    return res.status(err.status).json(toResponse(err));
  }

  // unknown error — log internally, hide details from client
  // stack trace is never sent to clients — security best practice
  console.error("[UNHANDLED ERROR]", err);
  res.status(500).json({
    status:  500,
    code:    "INTERNAL_SERVER_ERROR",
    message: "Something went wrong. Please try again.",
  });
});

A Complete Working Example

Here is a full register and login flow using all four modules:

// src/env.ts
import { envault } from "nodearmor/env";

export const env = envault({
  DATABASE_URL: { type: "string" },
  PORT:         { type: "number", default: 3000 },
  JWT_SECRET:   { type: "string" },
  NODE_ENV:     { type: "string", enum: ["development", "production", "test"] },
});


// src/routes/auth.ts
import { Router }                 from "express";
import { z }                      from "nodearmor";
import { hash, verify }           from "nodearmor/hash";
import { guard }                  from "nodearmor/guard";
import { Conflict, Unauthorized } from "nodearmor/errors";

const router = Router();

const RegisterSchema = z.object({
  email:    z.string().email(),
  password: z.string().min(8),
  name:     z.string().min(1),
});

const LoginSchema = z.object({
  email:    z.string().email(),
  password: z.string().min(1),
});

router.post("/register", guard(RegisterSchema), async (req, res) => {
  const { email, password, name } = req.body;

  const existing = await db.users.findByEmail(email);
  if (existing) throw new Conflict("Email already registered", { field: "email" });

  const passwordHash = await hash(password);
  const user = await db.users.create({ email, name, password: passwordHash });

  res.status(201).json({ id: user.id, email: user.email });
});

router.post("/login", guard(LoginSchema), async (req, res) => {
  const { email, password } = req.body;

  const user = await db.users.findByEmail(email);
  if (!user) throw new Unauthorized("Invalid email or password");

  const isValid = await verify(user.passwordHash, password);
  if (!isValid) throw new Unauthorized("Invalid email or password");

  const token = jwt.sign({ userId: user.id }, env.JWT_SECRET);
  res.json({ token });
});

export default router;


// src/app.ts
import express          from "express";
import { env }          from "./env";
import { errorHandler } from "./middleware/errorHandler";
import authRoutes       from "./routes/auth";

const app = express();
app.use(express.json());
app.use("/auth", authRoutes);
app.use(errorHandler);

app.listen(env.PORT, () => {
  console.log(`[${env.NODE_ENV}] Server running on port ${env.PORT}`);
});

Why One Package Instead of Three

bcrypt + joi + AppError nodearmor
Install Three separate commands npm install nodearmor
Extra dependencies Manual peer dep installs Everything included
Versions to track Three changelogs One
TypeScript support Mixed — some need @types Native throughout
Password algorithm bcrypt — 1999, CPU-only Argon2id — 2025, memory-hard
Error response shape Different in every project Always status + code + message + meta
Validation boilerplate 10-15 lines per route One line per route

Security

nodearmor v1.0.5 passes npm audit with zero vulnerabilities.

Argon2id default parameters follow OWASP 2025 recommendations:

  • Memory cost: 64 MB per hash attempt

  • Time cost: 3 iterations

  • Parallelism: 1 thread

Stack traces from your server are never included in HTTP error responses.

How to Get Started

npm install nodearmor

That is it. All dependencies are included automatically. No peer dependency installs needed.

Full documentation and source code:

If you find a bug or want to contribute, open an issue or pull request on GitHub. We review everything.

Final Thought

bcrypt still works. We are not saying it is broken. We are saying that in 2025, with GPU attacks being as cheap and accessible as they are, there is a better option that takes the same amount of code to use.

Argon2id is that option. nodearmor makes it the default.

One install. Four protections. Zero excuses for insecure backends.

Built by Shaswat and Ubed- real open source package.

If this helped you, share it with a developer who is still using bcrypt.

14 DeFi Protocols, One API: Building an AI DeFi Agent

Building DeFi applications today means juggling a dozen different APIs, SDKs, and integration patterns. Want to swap on Jupiter? One SDK. Lend on Aave? Different API. Stake with Lido? Yet another integration. Your trading bot ends up looking like a Frankenstein monster of protocol-specific code that breaks every time someone updates their interface.

This fragmentation isn’t just annoying—it’s expensive. Every new protocol integration costs weeks of development time. Testing across multiple chains doubles the complexity. And when you’re building AI agents that need to make split-second decisions across DeFi protocols, wrestling with 14 different APIs isn’t just inefficient, it’s prohibitive.

The Multi-Protocol Problem

Most DeFi developers face the same pattern: start with one protocol, then gradually add more as requirements grow. You begin with a simple Uniswap integration, then add Compound for lending, then Lido for staking. Each integration brings its own quirks:

  • Different authentication patterns
  • Inconsistent error handling
  • Varying gas estimation approaches
  • Protocol-specific transaction formats
  • Different slippage protection mechanisms

Before you know it, your clean codebase becomes a maze of protocol adapters, each with its own failure modes and edge cases. And that’s just on Ethereum—add Solana protocols like Jupiter and Drift, and you’re now maintaining two completely different tech stacks.

One API, 14 Protocols

WAIaaS solves this with a unified REST API that abstracts away protocol complexity while preserving full functionality. Instead of learning 14 different integration patterns, you make standard HTTP requests to execute actions across all major DeFi protocols.

Here’s the complete list of integrated protocols:

  • EVM: Aave v3, LI.FI, Lido, 0x, Pendle, Kamino
  • Solana: Jupiter, Drift, Jito Staking
  • Cross-chain: Across Protocol
  • Multi-chain: Hyperliquid, Polymarket
  • Specialized: D’CENT Swap, ERC-8004

The beauty is in the consistency. Whether you’re swapping on Jupiter or lending on Aave, the API pattern remains the same:

curl -X POST http://127.0.0.1:3100/v1/actions/jupiter-swap/swap 
  -H "Content-Type: application/json" 
  -H "Authorization: Bearer wai_sess_<token>" 
  -d '{
    "inputMint": "So11111111111111111111111111111111111111112",
    "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "amount": "1000000000"
  }'

Protocol Deep Dive: What’s Actually Integrated

Let’s break down what you can do with each protocol through the unified API:

Jupiter (Solana DEX Aggregator)

  • Token swaps with optimal routing
  • Slippage protection and MEV resistance
  • Real-time price discovery across Solana DEXs

Aave v3 (Lending Protocol)

  • Supply assets to earn yield
  • Borrow against collateral with dynamic interest rates
  • Health factor monitoring and liquidation protection

Hyperliquid (Perpetual Futures)

  • Perpetual contract trading with up to 50x leverage
  • Sub-account management for risk isolation
  • Real-time position and PnL tracking

Lido & Jito (Liquid Staking)

  • Stake ETH/SOL while maintaining liquidity
  • Automatic reward compounding
  • Unstaking queue management

LI.FI & Across (Cross-chain Bridges)

  • Asset transfers between 20+ chains
  • Optimal route selection for cost and speed
  • Bridge status monitoring and completion tracking

The key insight is that each protocol maintains its full feature set—you’re not getting a dumbed-down wrapper. You can access advanced features like Aave’s debt token operations or Hyperliquid’s sub-accounts through the same clean REST interface.

Building a Multi-Protocol Agent

Here’s where things get interesting for AI agents. With traditional integrations, building a bot that can arbitrage across protocols means managing multiple connection states, different error handling patterns, and inconsistent response formats.

With WAIaaS, your AI agent can execute complex strategies with simple HTTP calls:

// Multi-protocol strategy: stake SOL, bridge USDC, provide liquidity
const client = new WAIaaSClient({
  baseUrl: 'http://127.0.0.1:3100',
  sessionToken: process.env.WAIAAS_SESSION_TOKEN,
});

// 1. Stake SOL with Jito for yield
const stakeResult = await client.executeAction('jito-staking', 'stake', {
  amount: '10.0'
});

// 2. Bridge USDC to Ethereum via LI.FI  
const bridgeResult = await client.executeAction('lifi', 'bridge', {
  fromChain: 'solana',
  toChain: 'ethereum',
  token: 'USDC',
  amount: '5000.0'
});

// 3. Supply bridged USDC to Aave for lending yield
const lendResult = await client.executeAction('aave-v3', 'supply', {
  asset: '0xA0b86a33E6441A8DAB5F0C3B0C44c71b30E11Da3', 
  amount: '5000.0'
});

Each action returns a consistent response format with transaction status, fees, and completion tracking. No protocol-specific quirks to handle.

Real-World Performance

The unified approach shines when building production DeFi systems. Instead of maintaining separate monitoring, error handling, and retry logic for each protocol, you get:

  • Consistent Error Handling: All protocols return standardized error codes
  • Unified Gas Management: Automatic gas estimation across EVM protocols
  • Transaction Lifecycle: Same status polling pattern for all protocols
  • Policy Enforcement: Universal spending limits and security controls

This matters for AI agents that need to make autonomous decisions. Your agent can implement sophisticated strategies—like yield farming across multiple protocols or cross-chain arbitrage—without getting bogged down in integration complexity.

Security at Scale

Managing multiple DeFi protocols also means managing multiple attack vectors. Each protocol has its own security model, approval patterns, and risk profile. WAIaaS addresses this with a unified policy engine that works across all 14 protocols.

You can set protocol-specific limits:

curl -X POST http://localhost:3100/v1/policies 
  -H 'Content-Type: application/json' 
  -H 'X-Master-Password: <password>' 
  -d '{
    "walletId": "<wallet-uuid>",
    "type": "PERP_MAX_LEVERAGE", 
    "rules": {
      "maxLeverage": 10,
      "allowedMarkets": ["BTC-USD", "ETH-USD"]
    }
  }'

Or set universal spending limits that apply across all protocols:

{
  "type": "SPENDING_LIMIT",
  "rules": {
    "instant_max_usd": 100,
    "daily_limit_usd": 5000,
    "monthly_limit_usd": 50000
  }
}

This means your AI agent can safely operate across DeFi protocols without risking catastrophic losses from a single bad decision.

Getting Started: Your First Multi-Protocol Agent

Ready to try it? Here’s how to get a multi-protocol DeFi agent running in under 5 minutes:

  1. Install and Initialize
npm install -g @waiaas/cli
waiaas init
waiaas start
  1. Create Wallets and Sessions
waiaas quickset --mode mainnet  # Creates EVM + Solana wallets
  1. Fund Your Wallets
    Transfer some ETH and SOL to the generated addresses for gas and trading.

  2. Test Cross-Protocol Operations

# Check DeFi positions across all protocols
curl http://127.0.0.1:3100/v1/defi/positions 
  -H "Authorization: Bearer wai_sess_<your-token>"
  1. Execute Your First Multi-Protocol Strategy
    Use the TypeScript SDK or direct REST calls to execute actions across any of the 14 integrated protocols.

The complete setup takes minutes instead of weeks, and you immediately have access to the entire DeFi ecosystem through one consistent interface.

What’s Next

This unified approach opens up possibilities that weren’t practical before. AI agents can now implement sophisticated multi-protocol strategies, cross-chain arbitrage, and dynamic yield optimization without drowning in integration complexity.

The next step is to try it yourself. Clone the repository and see how quickly you can build DeFi functionality that would have taken months with traditional protocol-by-protocol integration.

Ready to unify your DeFi stack? Get started at GitHub or learn more at waiaas.ai.

Common bottlenecks that slow down enterprise applications

In the high-stakes world of enterprise software, speed is not just a feature—it is the foundation of user trust and operational efficiency. An application that lags, freezes, or times out does more than just frustrate employees; it bleeds revenue, hampers productivity, and damages brand reputation.

Modern enterprise architectures are complex, often involving a mesh of microservices, third-party APIs, hybrid cloud environments, and massive databases. While this complexity drives innovation, it also creates numerous hiding spots for performance bottlenecks. This is where enterprise application testing becomes critical, helping teams proactively identify performance issues, scalability limitations, and system vulnerabilities before they reach production.

Identifying these issues after a product launch is often too late and too costly. Through structured enterprise application testing, organizations can evaluate system behavior under real-world conditions, simulate user load, and detect hidden performance issues early in the development lifecycle.

This guide explores the most common performance bottlenecks in enterprise applications and provides actionable strategies to detect them before they impact your end users.

The Hidden Anchors: Top 5 Performance Bottlenecks

A bottleneck occurs when a single component in your application architecture limits the system’s overall capacity. Just as a narrow neck on a bottle restricts liquid flow, these technical constraints limit data flow and processing speed.

1. Database Inefficiencies
The database is often the first place performance engineers look, and for good reason. It is the heaviest lifter in most enterprise applications. Common database bottlenecks include:

  • Slow Queries: Queries that force the database to scan entire tables instead of using efficient indexes can bring an application to a crawl.
  • Connection Pool Exhaustion: If an application opens too many connections without closing them, or if the pool size is too small for the concurrent user load, requests will pile up waiting for an available connection.
  • Lock Contention: When multiple processes concurrently access or modify the same data, they can “lock” the data, causing other processes to wait.

2. Network Latency and Bandwidth Issues
In distributed systems, data rarely resides in a single location. It travels between servers, availability zones, and even continents.

  • High Latency: The delay before a data transfer begins after an instruction. In global enterprise apps, physical distance plays a role, but so do inefficient routing and poor network configurations.
  • Chatty Protocols: Applications that make numerous small requests to the server (rather than fewer large ones) introduce significant overhead, exacerbating latency issues.

3. Third-Party API Dependencies

Modern enterprise applications rarely stand alone; they rely on payment gateways, geolocation services, and CRM integrations.

  • The “Weakest Link” Problem: Your application might be optimized, but if a third-party API you rely on is experiencing downtime or slow response times, your users experience that delay directly.
  • Lack of Asynchronous Processing: If your application waits for a third-party response before loading the rest of the page (synchronous loading), a slow external API can freeze your entire interface.

4. Poorly Optimized Code and Algorithms
Sometimes the call is coming from inside the house. Inefficient coding practices can consume excessive CPU and memory resources.

Memory Leaks: If an application fails to release memory it no longer needs, it will eventually consume all available RAM, leading to a crash.

Inefficient Loops and Logic: Nested loops or complex algorithms running on the main thread can block user interactions, making the app feel unresponsive.

5. Resource Saturation (CPU and I/O)
Hardware limitations still matter in the cloud age.

  • Virtualization Overhead: In virtualized environments, “noisy neighbors” (other virtual machines on the same physical server) can hog resources, causing unpredictable performance dips for your application.
  • Disk I/O: High-volume transactional apps often hit a wall when disk read/write throughput cannot keep up with the data volume.

Strategies for Early Detection

Detecting bottlenecks requires a proactive mindset. Waiting for user complaints is not a strategy; it is a liability. You need to implement a “shift-left” approach, moving performance testing earlier in the development lifecycle.

1. Implement Comprehensive Application Performance Monitoring (APM)
APM tools are the stethoscope of your enterprise architecture. They provide real-time visibility into your application’s behavior. A robust APM solution traces transactions across distributed systems, visualizing exactly where time is spent, whether in the database, in code, or in an external API call.

2. Conduct Rigorous Load and Stress Testing
You cannot predict how your application will behave during Black Friday traffic based on Tuesday morning testing.

  • Load Testing: Verifies that the system can comfortably handle expected traffic volumes.
  • Stress Testing: Pushes the system beyond its limits to identify its breaking point and ensure a graceful recovery.

This is where selecting the right performance testing tools becomes critical. Tools like JMeter and Gatling let you simulate thousands of concurrent users, revealing bottlenecks that are invisible during manual testing.

3. Code Profiling During Development
Developers should not rely solely on QA to find performance issues. Code profilers can be used within an Integrated Development Environment (IDE) to analyze code runtime behavior. Profilers highlight methods that consume high CPU or memory, allowing developers to refactor inefficient code before it is ever committed to the main branch.

4. Real User Monitoring (RUM)
Synthetic testing (simulated users) is essential, but it doesn’t capture the chaos of the real world. RUM captures data from actual users navigating your application. It helps you detect bottlenecks that are specific to certain geographies, devices, or browser versions, variables that are often missed in a controlled test lab.

5. Prioritize Enterprise Application Testing
Enterprise application testing differs from standard app testing due to its scale and integration complexity. It requires a holistic strategy that includes:

  • End-to-End Transaction Testing: Validating that data flows correctly from the frontend through middleware to the backend and back.
  • Scalability Testing: Ensuring the architecture can scale up (add resources) or scale out (add more instances) automatically as demand increases.

Why “Works on My Machine” Isn’t Good Enough

One of the most significant challenges in detecting bottlenecks is the environment gap. A bottleneck might not appear in a high-speed corporate network test environment but might render the app unusable for a field employee using a mid-range smartphone on a 4G network.

Traditional emulators and simulators cannot fully replicate these real-world conditions. They often miss critical variables such as battery drain, network fluctuations, and CPU throttling that occur on real hardware. To truly detect bottlenecks early, you need to test on the devices and networks your customers actually use.

Conclusion

Bottlenecks are inevitable in complex software, but they don’t have to be fatal. By understanding the common choke points, from database queries to network latency, and arming your team with the right enterprise application testing strategies, you can maintain a high-performance ecosystem. Tools that offer deep visibility and real-world testing capabilities are no longer a luxury; they are a necessity for delivering the speed and reliability your enterprise users demand.

When standard software performance testing tools and emulators fall short, HeadSpin provides the critical “ground truth” needed for enterprise applications. HeadSpin offers a global device infrastructure that enables you to test your applications on thousands of real, SIM-enabled devices across over 50 locations worldwide.

Originally Published:- https://azbigmedia.com/business/common-bottlenecks-that-slow-down-enterprise-applications-and-how-to-detect-them-early/

How I Built 65+ AI Tools on a Raspberry Pi 5

There’s a gap in the MCP ecosystem that bothered me. If you want to give your AI coding assistant access to tools — web scraping, research, translation, agent memory — you either build everything yourself or cobble together a dozen different services. I wanted one MCP server that handled all of it.

So I built AiPayGen: 65+ tools, installable with pip install aipaygen-mcp, running on a Raspberry Pi 5 sitting on my desk.

Why I built it

MCP (Model Context Protocol) is how AI assistants like Claude Code, Cursor, and Cline access external tools. The protocol is solid, but the tool ecosystem is fragmented. Most MCP servers offer one thing — a file reader, a web search wrapper, a database connector. If you want a full toolkit, you’re managing ten different MCP servers.

I wanted a single pip install that gives an AI assistant access to research, writing, code generation, web scraping, data feeds, agent memory, and workflow orchestration. One server, one API key, one bill.

The architecture

The production stack is deliberately simple:

  • Hardware: Raspberry Pi 5, overclocked to 2.7GHz, with an NVMe SSD in a Pironman 5 case
  • Backend: Flask application with modular route blueprints
  • Database: SQLite for user metadata, billing, agent memory
  • MCP transport: FastMCP with streamable-http, built on MCP SDK 1.26
  • Tunnel: Cloudflare tunnel for HTTPS ingress
  • Package: Published to PyPI as aipaygen-mcp

Here’s the key architectural insight: the Pi doesn’t run AI models. It routes requests. When you call the research tool, the Pi validates your API key, checks your balance, selects the best upstream model for the task (from 15 models across 7 providers), forwards the request, and deducts the cost.

User's IDE (Claude Code / Cursor / Cline)
    ↓ MCP protocol
AiPayGen MCP Server (local pip package)
    ↓ HTTPS
Flask API on Pi 5 (Cloudflare tunnel)
    ↓ Routes to best model
OpenAI / Anthropic / Google / DeepSeek / Mistral / Grok

The tools

The 65+ tools break down into categories:

AI tools (40+): Research, summarize, write, translate, code, analyze, sentiment, classify, extract, compare, explain, plan, decide, debate, proofread, rewrite, pitch, headline, keywords, outline, and more. Each tool has a specific prompt template optimized for its task, and the router picks the best model automatically.

curl -X POST https://api.aipaygen.com/summarize 
  -H "Authorization: Bearer apk_YOUR_KEY" 
  -H "Content-Type: application/json" 
  -d '{"text": "Your long article text here...", "length": "short"}'

Web scraping (6 tools): Google Maps businesses, Twitter/X posts, Instagram profiles, TikTok videos, YouTube transcripts, and generic website scraping.

Agent infrastructure: Persistent memory (key-value with full-text search), agent-to-agent messaging, shared task boards, a knowledge base with RAG search, and a skills marketplace.

Free data feeds: Weather, crypto prices, exchange rates, holidays. No API key needed.

curl "https://api.aipaygen.com/data/weather?city=London"
curl "https://api.aipaygen.com/data/crypto?symbols=bitcoin,ethereum"

The x402 payment experiment

Alongside traditional Stripe billing, I integrated the x402 protocol for HTTP-native micropayments. You make an API call, the server responds with HTTP 402 and includes the price in the headers. Your client signs a USDC transaction and retries with a payment receipt. No signup, no API key, no account. Just a crypto wallet.

In practice, most users still prefer Stripe. But the x402 integration has real potential for agent-to-agent commerce where there’s no human to enter a credit card.

Challenges and what I learned

Model routing is harder than it sounds. Different models are better at different tasks. I built a routing layer that picks the model based on the tool being called, but there’s room for improvement.

Scraping is a maintenance nightmare. Platforms change their markup regularly. Budget significant time for maintenance.

SQLite is underrated for this scale. SQLite with WAL mode handles concurrent reads fine. Having the database as a single file makes backups trivial.

MCP is a genuine distribution channel. People discover tools through their IDE. The friction is dramatically lower than traditional API integration.

The Pi 5 is surprisingly capable. Four months later, it’s still handling production traffic at 2.7GHz. The NVMe SSD was the critical upgrade.

Try it

pip install aipaygen-mcp
claude mcp add aipaygen -- aipaygen-mcp

Two commands, free trial credits, no card needed. If you find it useful, I’d like to hear about it. If you don’t, I’d really like to hear about that — the honest feedback is more valuable.

I built GitKingdom, where GitHub becomes a fantasy kingdom

https://www.gitkingdom.com

It takes GitHub data and transforms it into an explorable pixel-art RPG world, languages become kingdoms, repositories become buildings, and contributors become citizens with RPG stats.

How it works:

  • Each programming language gets its own kingdom territory on the world map
  • Repos become buildings — more stars = bigger building (hovels → cottages → castles → citadels)
  • Contributors become citizens with RPG titles based on their commit rank
  • Top contributor to each repo is crowned King
  • Sign in with GitHub to claim your repos and see your kingdom
  • Anyone can add any public repo with 1+ stars

Try it now:

  • Sign in with GitHub to claim your repos and find your buildings on the map

Current state:

  • 13 kingdoms (TypeScript, Python, Rust, Go, Java, etc.)
  • Thousands of repos already mapped
  • Citizen profile pages with RPG stats and badges
  • Explorable Phaser 3 game with zoom, pan, cities

Tech stack (for the curious):

  • Phaser 3 game engine + TypeScript
  • Procedural world generation (landmass, elevation, biomes)
  • Vercel serverless + Supabase Postgres
    • GitHub API with multi-token pooling
  • Pre-baked world JSON + delta sync for fast loads

Looking for:

  • Repos to add – the more repos, the bigger the world gets
    • Feedback
    • Bug reports
    • Game artists
    • Ideas for new features (quests? building interiors? battles between kingdoms?)

https://www.gitkingdom.com

We got burned by silent API changes twice this year. how do you handle this?

Our team builds on a Next.js stack and depends on a
handful of third-party APIs some AI providers, some
infrastructure, some data services.

Twice this year, we got user complaints before we even
knew something was broken.

The culprit both times? Silent breaking changes from
upstream APIs. Once it was a renamed field in a response
object. Another time, a deprecated endpoint that stopped
returning data the way we expected.

We weren’t monitoring changelogs actively. Nobody on the
team had time to read through every API provider’s release
notes. And by the time the breakage surfaced, it had
already affected real users.

It got me thinking, this must happen to other teams too,
especially now that most startups are stitching together
5-10 APIs just to ship their core product. AI-native teams
have it even worse, model versions deprecate fast, output
formats change, endpoints get retired.

So I’m genuinely curious:

How do you stay ahead of breaking changes from APIs
you depend on?

  • Do you monitor changelogs manually?
  • Have you built any internal tooling for this?
  • Do you run contract tests against third-party APIs?
  • Or do you just find out when users complain?

Just had this pain fresh in my mind and
wanted to hear how others are dealing with it. Would love
to hear what’s worked (or hasn’t).