🚀 Cursor Just Dropped Composer 2.5: Why The AI Coding War Just Got Serious

If you thought AI coding assistants were already moving fast, buckle up. Cursor just dropped Composer 2.5, their smartest and most capable coding model yet.

While previous iterations were great at churning out boilerplate, Composer 2.5 represents a massive leap in handling long-horizon coding work. We are talking about the kind of complex, multi-step problems that take hundreds of tool calls to get right.

Here is everything you need to know about the update, the tech behind it, and why it is a big deal for developers. 👇

🧠 Smarter on the Hard Stuff

The biggest bottleneck with AI coding tools has always been sustained context. They start strong, but lose the plot after a few files.

Cursor tackled this head-on by scaling their training significantly. Composer 2.5 was trained on 25x more synthetic RL (Reinforcement Learning) tasks than its predecessor.

However, simply scaling data is not enough. When a model’s rollout spans hundreds of thousands of tokens, it becomes incredibly difficult to assign credit—meaning the AI struggles to know which specific decision helped or hurt the outcome.

To fix this, Cursor introduced targeted textual feedback during RL.

Instead of waiting for the end of a rollout to penalize the model, they provide feedback directly at the exact point where the model messed up. For example, if the model makes a bad tool call or provides a confusing explanation, it receives a localized hint describing the desired improvement. This shapes crucial behaviors like communication style and effort calibration, making the AI genuinely more pleasant to collaborate with.

💻 The Code: How to Leverage Long-Horizon Agents

Because Composer 2.5 is built for sustained work, you can give it much more complex architecture tasks. Instead of asking for a single function, you can set up a .cursorrules file to define a long-running agentic workflow.

Here is an example of how you might instruct a long-horizon model like Composer 2.5 to autonomously refactor a legacy codebase:

# .cursorrules

You are an expert systems architect. Your task is to refactor the legacy `auth` module into a modern, scalable service.

## Workflow Execution Steps:
1. **Analyze:** Read all files in the `/src/legacy_auth` directory.
2. **Plan:** Draft a migration plan and wait for my approval before writing code.
3. **Execute:** Implement the new JWT-based auth flow across all middleware.
4. **Test:** Generate unit tests for the new implementation. 
5. **Verify:** Run the tests using the terminal tool. If any fail, autonomously fix the errors until all tests pass.

*Note: If you encounter a missing dependency, use the terminal to install it.*

With Composer 2.5’s improved tool use and behavioral shaping, it can actually execute a multi-step loop like this without hallucinating halfway through.

🏗️ The Elephant in the Room: The Kimi Base

There was a lot of community noise around Composer 2 being built on top of Moonshot AI’s open-source Kimi K2.5 checkpoint. Cursor acknowledged this, and confirmed that Composer 2.5 also builds on the same Kimi K2.5 open-source checkpoint.

However, Cursor’s secret sauce is their post-training. The continued pretraining and massive RL pipeline are what give Composer its specific developer-centric “feel”.

💸 The Pricing is Absurdly Good

Despite matching or beating frontier models on benchmarks, Cursor has kept the price aggressively low.

Composer 2.5 is priced identically to Composer 2: $0.50 per million input tokens and $2.50 per million output tokens. This is a fraction of the cost of OpenAI’s GPT-5.5 or Anthropic’s Opus 4.7.

🚀 What’s Next: The SpaceXAI Collab

Cursor is not stopping here. They announced that they are currently working with SpaceXAI to train a significantly larger model entirely from scratch. Using 10x more total compute on the Colossus 2 supercomputer, this upcoming model is expected to be a massive leap in capability.

Are you using Composer 2.5 yet? Drop your thoughts in the comments below! 👇

Is Gemini 3.5 Flash Actually Better at Coding Than 3.1 Pro? I Tested It with Real Rust Code

Background

Gemini 3.5 Flash launched at Google I/O 2026 with a bold claim: it beats Gemini 3.1 Pro on coding and agentic benchmarks — while running 4x faster.

At the same time, X (formerly Twitter) is full of posts saying it hallucinates constantly and doesn’t even reach Claude Sonnet level.

So which is it? I ran a real benchmark using code from my actual dev stack to find out.

Who I am

  • Solo indie Mac app developer (Tauri + Rust + Swift stack)
  • I use Gemini daily as part of my coding workflow
  • Built 13 macOS utilities, mostly Android connectivity tools

The Test

Models compared

  • Gemini 3.1 Pro
  • Gemini 3.5 Flash (new)

What I tested

I gave both models a ~200-line Rust file (ADB device manager) with 14 intentional bugs and asked them to find and fix everything.

Why 200 lines? Because in my experience:

  • Under 50 lines: any model gets lucky sometimes
  • Over 100 lines: older Flash models produce near-unusable code
  • 200 lines: a realistic production task that separates real understanding from pattern matching

Bug breakdown

Category Count Examples
Logic bugs (main) 7 Post-execution timeout check, APK install failure not detected
Async bugs 2 CPU-spinning busy loop, potential data race
ADB-specific traps 3 r leftover in line endings, temp file not cleaned up
Missing tests 2 Edge cases not covered

The ADB-specific bugs were key — you need domain knowledge to catch them, not just Rust syntax awareness.

Prompt (no hints)

The following Rust code contains several bugs.
Please identify all bugs and provide the corrected code.
Include an explanation for each bug.

No hints. No scaffolding. Raw capability test.

The Code (buggy version)

adb_device_manager.rs — click to expand

use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex};
use tokio::time::sleep;
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct AdbDevice {
    pub serial: String,
    pub state: DeviceState,
    pub properties: HashMap,
}

#[derive(Debug, Clone, PartialEq)]
pub enum DeviceState {
    Online, Offline, Unauthorized, Unknown,
}

pub struct AdbManager {
    devices: Arc>>,
    adb_path: String,
    command_timeout: Duration,
}

impl AdbManager {
    pub fn new(adb_path: String) -> Self {
        AdbManager {
            devices: Arc::new(Mutex::new(Vec::new())),
            adb_path,
            // BUG 1: from_millis(5) — should be from_secs(5)
            command_timeout: Duration::from_millis(5),
        }
    }

    pub fn execute_command(&self, serial: &str, args: &[&str]) -> Result {
        let start = Instant::now();

        // prepend "-s " before command args
        let mut cmd_args = vec!["-s", serial];
        cmd_args.extend_from_slice(args);

        let output = Command::new(&self.adb_path)
            .args(&cmd_args)
            .output()
            .map_err(|e| format!("Command failed: {}", e))?;

        // BUG 4: timeout check AFTER command completes — completely useless
        if start.elapsed() > self.command_timeout {
            return Err("timed out".to_string());
        }
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    pub async fn wait_for_device(&self, serial: &str, timeout_secs: u64) -> Result<(), String> {
        let deadline = Instant::now() + Duration::from_secs(timeout_secs);
        loop {
            // BUG 8: no sleep → CPU at 100%
            let devices = self.get_connected_devices()?;
            if devices.iter().any(|d| d.serial == serial) {
                return Ok(());
            }
            if Instant::now() >= deadline {
                return Err("timeout".to_string());
            }
        }
    }

    pub fn install_apk(&self, serial: &str, apk_path: &str) -> Result<(), String> {
        let result = self.execute_command(serial, &["install", "-r", apk_path])?;
        // BUG 11: adb install returns exit code 0 even on failure
        // must check stdout for "Success"/"Failure" strings
        if result.contains("Failure") {
            return Err(format!("Install failed: {}", result));
        }
        Ok(())
    }

    pub fn take_screenshot(&self, serial: &str, save_path: &str) -> Result<(), String> {
        let temp_path = "/sdcard/screenshot_temp.png";
        self.execute_command(serial, &["shell", "screencap", "-p", temp_path])?;
        // BUG 12: temp file never deleted from device
        self.execute_command(serial, &["pull", temp_path, save_path])?;
        Ok(())
    }
}

Results

Bug detection

Model Bugs found Score
Gemini 3.1 Pro 14 14/14 ✅
Gemini 3.5 Flash 14 14/14 ✅

Both models found every bug. Accuracy: identical.

Speed

Model Response time
Gemini 3.1 Pro ~40 seconds
Gemini 3.5 Flash A few seconds (10x+ faster)

This was the most striking difference by far.

Where the models diverged

Same score, but different approaches on a few interesting bugs:

Bug 4 — timeout check after execution

3.1 Pro rewrote it using tokio::time::timeout (fully async)

3.5 Flash used spawn() + try_wait() polling loop (sync-leaning approach)

Both are valid fixes. Different style choices.

Bug 10 — Mutex poison handling

3.1 Pro: into_inner() to safely recover the data

3.5 Flash: expect() for fail-fast behavior

Opposite design philosophies. Neither is wrong — depends on your error handling strategy.

Bug 6 — spaces in remote path

3.1 Pro: correctly noted that Command::new handles args without shell splitting, so no quoting needed — left it as-is (accurate ADB knowledge)

3.5 Flash: added format!(""{}"", remote_path) quoting (technically unnecessary, slight overreach)

3.1 Pro showed deeper understanding of how ADB + Rust process spawning actually works.

Pricing reality check

App (free plan)

Model Cost Speed
3.1 Pro Free Slow (~40s)
3.5 Flash Free Fast (few seconds)

API (Pay-as-you-go)

Straight from Google AI Studio’s official UI:

Model Input / 1M tokens Output / 1M tokens
Gemini 3.5 Flash $1.50 $9.00
Gemini 3.1 Flash-Lite $0.25 $1.50
Context Caching $0.15 —

The $9.00 output price is 3x the previous generation (Gemini 3 Flash at $3.00). Google’s “half the price of frontier models” pitch compares against competitors — not their own previous Flash tier.

For indie developers:

  • Prototyping / testing → Free tier is more than enough
  • Production / commercial → $1.50/$9.00. Budget carefully for output-heavy workloads.

Bonus: I asked Gemini about its own price. It hallucinated.

During testing I asked Gemini 3.5 Flash directly: “What’s the API pricing for Gemini 3.5 Flash?”

It confidently answered:

“Input: ~$0.50 / Output: ~$3.00 per million tokens!”

That’s the old Gemini 3 Flash Preview pricing. The actual price is $1.50/$9.00.

When I told it the real number, it immediately replied:

“I sincerely apologize! The information you provided is 100% correct!”

The model that aced a 14-bug Rust challenge couldn’t accurately describe its own pricing.

A hallucination detection benchmark article ending with a hallucination felt appropriate.

Conclusion

Category Result
Coding accuracy (200-line bug fix) 3.1 Pro ≈ 3.5 Flash
Output speed 3.5 Flash wins by 10x+
API cost (output) 3.5 Flash at $9.00/1M tokens (3x previous gen)
Free tier usability 3.5 Flash is the clear winner

For free tier users: switch to 3.5 Flash immediately.

For API cost-conscious production use: consider 3.1 Flash-Lite at $0.25/$1.50.

On the “doesn’t reach Claude Sonnet” criticism — at least for Rust bug-fix tasks, both Flash models performed at a level I’d call genuinely useful. The hallucination complaints may apply more to conversational/knowledge tasks than structured code review — though in my limited testing with a single task type, I can’t say for certain.

I build macOS utilities for Mac×Android workflows. If you’re into Tauri, ADB, or MTP on macOS, feel free to follow.

The Agent Payments Market Just Got Validated — Here’s What Everyone’s Missing

This week confirmed what we’ve been building toward: the agent payments market is real.

Visa partnered with Inflow to launch cards for AI agents. Circle released Agent Stack. Sapiom closed a $15M seed round for agent payment infrastructure. Sygnum Bank executed live AI agent-powered digital asset transactions.

The market has spoken. Autonomous agents will transact at scale.

But there’s a structural question buried in this news that nobody’s asking: who controls the keys?

The Retrofit vs. Native Divide

Card networks are doing what they do best—extending existing infrastructure to new users. Visa’s agent card is a logical move. Same issuer rails, same dispute resolution, same compliance frameworks. Just automated the cardholder.

But here’s the problem: a Visa card for an AI agent still requires a human to dispute a charge.

Card infrastructure assumes human fallback. Someone to call the bank. Someone to approve exceptions. Someone to verify fraud. The entire paradigm is built on human-in-the-loop escalation.

That works when you’re adding AI capabilities to human workflows. It breaks when agents ARE the workflow.

The Architectural Question

There are two paths for agent payments:

Path 1: Retrofit human infrastructure

  • Issue cards to agents
  • Use existing authorization networks
  • Layer agent logic on top of human compliance processes
  • Keep humans in the escalation path

Path 2: Build agent-native infrastructure

  • Give agents direct wallet custody via MPC
  • Authorization happens in code, not through issuer approval
  • Policy constraints are programmatic, not manual
  • No human required for normal operation

These aren’t feature differences. They’re architectural ones.

Why MPC Wallets Are Structurally Different

Multi-Party Computation wallets use threshold signing. A 2-of-3 scheme means no single party holds the complete private key. The agent can authorize transactions, but within policy boundaries enforced by the key structure itself.

At AgentWallex, we use this for agent custody:

  • Agent holds one key share
  • Policy engine holds another
  • Recovery mechanism holds the third

The agent can transact autonomously. But it CAN’T exceed spending limits, pay unauthorized recipients, or bypass rate caps—because the policy share won’t sign.

No approval queue. No manual review. No human fallback for normal operation.

Speed as a Structural Advantage

Our payment authorization completes in under 150ms. Card network authorization takes 2-3 seconds on average—and that’s before settlement.

This isn’t optimization. It’s architecture.

When you don’t need to route through issuer networks, check human approval queues, or wait for dispute resolution infrastructure to validate, you can move at the speed of cryptographic signing.

For x402 micropayments—pay-per-API-call billing—this matters. An agent calling 50 APIs per task can’t wait 3 seconds per payment. The task would take minutes instead of seconds.

What Agent-Native Actually Means

Sygnum Bank just completed live transactions using AI agents. These aren’t demos. They’re production.

We have 3,600 teams on our waitlist. Developers building with LangChain, CrewAI, AutoGPT—they’re not asking for cards. They’re asking for wallets their agents can use directly.

The pattern is consistent:

  • Agents need to pay for API calls autonomously
  • Manual approval breaks the workflow
  • Card disputes don’t make sense when the “cardholder” is code
  • Custody needs to stay with the agent, not the issuer

This is what we mean by agent-native. Not “better UX for humans managing agents.” Actual infrastructure agents can use without human intervention.

The Standards Layer

Stripe, Visa, and Tempo are building the Micropayment Protocol (MPP). Coinbase is implementing x402. These are the HTTP-level standards for agent payments.

We support them. We’re building the wallet and gateway layer ABOVE these protocols.

Standards define how payments are requested and formatted. Wallets define who holds custody and how authorization happens. Both are necessary. Neither replaces the other.

What This Week’s News Means

The funding rounds and partnerships validate the market. Enterprises believe agents will transact. Capital is flowing to infrastructure.

But validation of the market doesn’t answer the architectural question.

Do agents get retrofitted into human payment systems—cards, approval queues, dispute hotlines?

Or do we build infrastructure that works the way agents work—autonomous, programmatic, fast?

Both paths will exist. Card networks have distribution and compliance infrastructure that’s hard to replicate. For certain use cases, issuer control is exactly what you want.

But for true agent autonomy—agents that earn, spend, and transact without a human in the loop—you need wallet custody, not card issuance.

What We’re Building

AgentWallex is the payment gateway for AI agents. Not a card issuer. Not a bank. A gateway.

Agents get MPC wallets with policy constraints. They authorize payments in under 150ms. They pay per API call via x402. They operate within programmatic guardrails—no manual approvals required.

We’re live in sandbox at app.agentwallex.com. Production agents are already transacting.

The market just got validated. Now the architectural question matters.

Who controls the keys?

AgentWallex is building the payment infrastructure for autonomous agents. MPC-secured wallets, sub-150ms authorization, x402 native micropayments. 3,600+ teams on the waitlist. Start building at app.agentwallex.com.

Follow & Try AgentWallex

One Open Source Project a Day (No. 70): Claude Plugins Official — A Complete Tour of Anthropic’s Official Claude Code Plugin Ecosystem

Introduction

“Plugins extend Claude Code with commands, agents, skills, hooks, and MCP servers.”

This is the NO.70 article in the “One Open Source Project a Day” series. Today we are exploring claude-plugins-official.

This is Anthropic’s officially maintained plugin registry on GitHub. If you have been using Claude Code lately, you have almost certainly seen the /plugin install command — this repository is the official plugin source behind that command.

20.2k Stars, 2.5k Forks, 669 open issues. Behind these numbers is a rapidly maturing ecosystem: Anthropic engineers have contributed 30+ plugins covering LSP support for 12 languages, a multi-agent PR review toolkit, Git workflow automation, and code quality analysis. 15 external partners — including GitHub, Firebase, Linear, and Terraform — have already joined.

But this article is not just about “what plugins exist.” What deserves equal attention is the design of the plugin specification itself: a single plugin.json file, three extension mechanisms (Skills/Commands/MCP), and two trigger modes (user-invoked vs. model-invoked). Understanding this architecture means understanding the boundaries and possibilities of Claude Code extensibility.

What You Will Learn

  • The complete plugin directory structure of claude-plugins-official (internal vs. external plugins)
  • The Claude Code plugin specification: from plugin.json to Skills/Commands/MCP
  • Deep dives into five key plugins: pr-review-toolkit, agent-sdk-dev, code-review, hookify, commit-commands
  • How to build a spec-compliant Claude Code plugin from scratch
  • The current state of the external partner plugin ecosystem

Prerequisites

  • Familiarity with Claude Code (basic slash command usage)
  • A basic understanding of MCP (Model Context Protocol)
  • Interest in building your own Claude Code extensions

Project Background

Project Introduction

claude-plugins-official is Anthropic’s official plugin registry, serving the Claude Code plugin ecosystem. It plays two roles simultaneously:

  1. Plugin registry: Users install plugins via /plugin install <name>@claude-plugins-official
  2. Development specification reference: plugins/example-plugin is the official complete reference implementation, demonstrating all extension mechanisms

The repository is divided into two main directories:

  • /plugins: Developed by Anthropic engineers, covering LSP, development workflows, code quality, output styles, and more
  • /external_plugins: Submitted by partners and the community, subject to quality and security review

Author/Team Introduction

This is a multi-contributor Anthropic internal project, with different engineers leading each plugin:

Project Stats

  • ⭐ GitHub Stars: 20,200+
  • 🍴 Forks: 2,500+
  • 👁️ Watchers: 147
  • 🐛 Open Issues: 669
  • 💻 Languages: Python (31.6%), TypeScript (28.9%), HTML (19.5%), Shell (13.0%), JavaScript (7.0%)
  • 🏷️ Topics: skills, mcp, claude-code
  • 🌐 Repository: anthropics/claude-plugins-official
  • 📖 Docs: code.claude.com/docs/en/plugins

Main Features

Core Utility

claude-plugins-official provides a standardized way to extend Claude Code’s capabilities:

Native Claude Code capabilities
        ↓
  /plugin install <name>@claude-plugins-official
        ↓
  Extended Claude Code
  ├── New Slash Commands (user-invoked)
  ├── New Skills (model-auto-triggered)
  ├── New Agents (specialized task delegates)
  └── New MCP Tools (external service integrations)

Quick Start

Installing plugins (CLI):

# PR review toolkit
/plugin install pr-review-toolkit@claude-plugins-official

# Git commit command suite
/plugin install commit-commands@claude-plugins-official

# Agent SDK development tools
/plugin install agent-sdk-dev@claude-plugins-official

# Code review plugin
/plugin install code-review@claude-plugins-official

# Hook management tool
/plugin install hookify@claude-plugins-official

Installing plugins (UI):

In Claude Code, type: /plugin
→ Click "Discover"
→ Browse and install desired plugins

Installing external partner plugins:

# GitHub integration
/plugin install github@claude-plugins-official

# Linear project management
/plugin install linear@claude-plugins-official

# Firebase integration
/plugin install firebase@claude-plugins-official

# Terraform infrastructure
/plugin install terraform@claude-plugins-official

Plugin Directory Overview

Internal Plugins (/plugins)

Category Plugins
LSP Language Support clangd-lsp, csharp-lsp, gopls-lsp, jdtls-lsp, kotlin-lsp, lua-lsp, php-lsp, pyright-lsp, ruby-lsp, rust-analyzer-lsp, swift-lsp, typescript-lsp
Development Workflows agent-sdk-dev, claude-code-setup, commit-commands, feature-dev, mcp-server-dev, plugin-dev, pr-review-toolkit, ralph-loop
Code Quality code-modernization, code-review, code-simplifier, security-guidance
Output Styles explanatory-output-style, learning-output-style
Utility Tools claude-md-management, cwc-makers, frontend-design, hookify, math-olympiad, session-report, skill-creator
Reference example-plugin

External Partner Plugins (/external_plugins): asana, context7, discord, fakechat, firebase, github, gitlab, greptile, imessage, laravel-boost, linear, playwright, serena, telegram, terraform

Five Key Plugins: Deep Dives

Plugin 1: pr-review-toolkit — Six Parallel PR Review Agents

This is arguably the most elegantly designed plugin in the entire directory: 6 specialized agents running in parallel, reviewing the same Pull Request from different angles simultaneously.

PR submitted
  ↓
comment-analyzer      ← Comment accuracy, documentation completeness, comment rot
pr-test-analyzer      ← Test coverage quality, edge cases, behavioral vs. line coverage
silent-failure-hunter ← Silent failures, empty catch blocks, missing error logging
type-design-analyzer  ← Type encapsulation, invariants (rated 1–10 on 4 dimensions)
code-reviewer         ← CLAUDE.md compliance, style, bugs (0–100 score)
code-simplifier       ← Readability, unnecessary complexity, redundant abstractions

Natural language triggering:

"Are the tests thorough?"                  → triggers pr-test-analyzer
"Check the error handling in the API client" → triggers silent-failure-hunter
"Is this documentation accurate?"           → triggers comment-analyzer
"Simplify this code"                        → triggers code-simplifier

Full PR review (trigger all agents at once):

"I'm ready to create this PR. Please:
1. Review test coverage
2. Check for silent failures
3. Verify code comments are accurate
4. Review any new types
5. General code review"

Recommended workflow:

Write code → code-reviewer
Fix issues → silent-failure-hunter (if error handling changed)
Add tests  → pr-test-analyzer
Document   → comment-analyzer
Polish     → code-simplifier
→ Create PR

Plugin 2: agent-sdk-dev — Agent SDK Project Scaffolding

This plugin compresses “building a Claude Agent SDK project from scratch” into a single command.

/new-sdk-app command (interactive project creation):

/new-sdk-app my-agent-project
# Interactive prompts:
# 1. Language: TypeScript or Python?
# 2. Agent type: coding / business / custom
# 3. Starting point: minimal / basic / specific example
# 4. Package manager: npm/yarn/pnpm or pip/poetry

What it does automatically:

  • Checks and installs the latest SDK version
  • Creates project file structure, .env.example, .gitignore
  • Runs type checking (TS) or syntax validation (Python)
  • Automatically runs the appropriate Verifier agent (validates the project against SDK best practices)

Two Verifier Agents:

# Python project verification
"Verify my Python Agent SDK application"
→ Checks: SDK installation, requirements.txt/pyproject.toml,
           SDK usage patterns, agent init/config, .env security, error handling

# TypeScript project verification
"Verify my TypeScript Agent SDK application"
→ Checks: SDK installation, tsconfig.json, type safety/imports,
           agent init/config, .env security, error handling

Verifier output format:

Overall Status: PASS / PASS WITH WARNINGS / FAIL
- Critical Issues (blocking functionality)
- Warnings (suboptimal patterns)
- Passed Checks
- Recommendations (with SDK documentation links)

Plugin 3: code-review — Confidence-Filtered 4-Agent Code Review

This plugin addresses the most common problem with AI code review: too many false positives.

How it works:

1. Pre-checks — Skip closed, draft, or trivial PRs
2. Collect CLAUDE.md guideline files from the repo
3. Summarize PR changes
4. Launch 4 parallel agents:
   - Agent #1 & #2 → CLAUDE.md compliance checks
   - Agent #3 → Bug detection (changed code only)
   - Agent #4 → Git blame/history context analysis
5. Score each issue 0–100 for confidence
6. Filter out issues below threshold (default: 80)
7. Post review comment with high-confidence issues only

Confidence scale:

Score Meaning
0 Not confident, likely false positive
25 Somewhat confident, might be real
50 Moderately confident, real but minor
75 Highly confident, real and important
100 Absolutely certain, must fix

What gets filtered out:

  • Pre-existing issues not introduced by this PR
  • Code that looks buggy but isn’t
  • Issues that linters will catch
  • Items with lint-ignore comments

Configuring the confidence threshold (in commands/code-review.md):

# Change 80 to your preferred threshold
Filter out any issues with a score less than 80.

Plugin 4: hookify — Create Claude Code Hooks in Plain English

Claude Code’s Hooks feature (triggering custom logic before/after tool executions) requires manually editing complex JSON configuration. hookify eliminates that friction.

Core commands:

# Create a rule from plain English
/hookify Warn me when I use rm -rf commands

# Analyze recent conversation, auto-suggest behaviors to block
/hookify

# List all rules
/hookify:list

# Enable/disable rules interactively
/hookify:configure

Rules take effect immediately — no restart required.

Rule file format (stored as .claude/hookify.<name>.local.md):

---
name: block-dangerous-rm
enabled: true
event: bash
pattern: rms+-rf
action: block
---

⚠️ **Dangerous rm command detected!**

Please verify the path and ensure you have backups.

Advanced rules (multiple conditions):

---
name: warn-sensitive-files
enabled: true
event: file
action: warn
conditions:
  - field: file_path
    operator: regex_match
    pattern: .env$|credentials|secrets
  - field: new_text
    operator: contains
    pattern: KEY
---

🔐 **Sensitive file edit detected!**

Event types and when they fire:

event Fires when
bash Before a Bash command executes
file On file read/write operations
prompt When the user submits a prompt
stop When Claude is about to stop responding
all All of the above

Practical rule examples:

# Block destructive commands
event: bash
pattern: rms+-rf|dds+if=|mkfs|format
action: block

# Warn about debug code
event: file
pattern: console.log(|debugger;
action: warn

# Require tests before stopping
event: stop
action: block
conditions:
  - field: transcript
    operator: not_contains
    pattern: npm test|pytest|cargo test

# Prevent hardcoded API keys in TypeScript
event: file
conditions:
  - field: file_path
    operator: regex_match
    pattern: .tsx?$
  - field: new_text
    operator: regex_match
    pattern: (API_KEY|SECRET|TOKEN)s*=s*["']

Plugin 5: commit-commands — Three-Command Git Workflow

# Auto-generate commit message and commit
/commit

# Full one-command workflow: commit → push → create PR
/commit-push-pr

# Clean up local branches whose remotes have been deleted
/clean_gone

What /commit does:

  1. Analyzes staged and unstaged changes
  2. Reviews recent commit history to match repo style
  3. Stages files and creates a commit message with Claude Code attribution
  4. Automatically skips sensitive files (.env, credentials.json, etc.)

What /commit-push-pr does:

  1. Creates a new branch if currently on main
  2. Commits the changes
  3. Pushes to origin
  4. Creates a PR via GitHub CLI (includes Summary + test checklist)

Plugin Specification Deep Dive

Standard Plugin Directory Structure

plugin-name/
├── .claude-plugin/
│   └── plugin.json      # Plugin metadata (required)
├── .mcp.json            # MCP server config (optional)
├── skills/              # Skill definitions (preferred)
│   ├── my-skill/
│   │   └── SKILL.md     # Model-auto-triggered skill
│   └── my-command/
│       └── SKILL.md     # User-invoked slash command (also uses SKILL.md)
├── commands/            # Slash commands (legacy format)
│   └── my-command.md
├── agents/              # Agent definitions
└── README.md

The plugin.json Metadata Format

{
  "name": "my-plugin",
  "description": "A description of what this plugin does",
  "author": {
    "name": "Your Name",
    "email": "[email protected]"
  }
}

Deliberately minimal by design: only three fields — no version, no dependency declarations. A plugin’s capabilities are determined by its directory contents, not its metadata.

Three Extension Mechanisms Compared

Mechanism 1: Skills (Recommended for new plugins)

# Model-auto-triggered (context-based)
---
name: security-review
description: Automatically trigger security review when security-related code is detected
version: 1.0.0
---

# User-invoked (becomes /skill-name command)
---
name: my-command
description: Short description shown in /help
argument-hint: <arg1> [optional-arg]
allowed-tools: [Read, Glob, Grep]
---

Mechanism 2: Commands (Legacy format, functionally equivalent)

# commands/my-command.md
---
# YAML frontmatter
---
# Command content (same as SKILL.md)

Mechanism 3: MCP Servers (External service integration)

// .mcp.json
{
  "my-service": {
    "type": "http",
    "url": "https://api.myservice.com/mcp"
  }
}

The Key Difference Between Two Skill Trigger Modes

Trigger Type Activation Best For
User-invoked User types /skill-name Explicit one-time operations (commit, review)
Model-auto-triggered Claude judges from task context Continuous context enhancement (output styles, security checks)

This distinction is critical: the frontend-design plugin is a perfect example of model-auto-triggered — the user simply says “create a dashboard” and Claude automatically applies that plugin’s design principles, no explicit invocation required.

External Plugin Submission Process

To get your own plugin listed in the official directory:

1. Ensure your plugin meets quality and security standards
   ↓
2. Visit https://clau.de/plugin-directory-submission
   ↓
3. Fill out the plugin submission form
   ↓
4. Anthropic review (quality + security)
   ↓
5. Merged into /external_plugins directory

Review criteria (inferred from documentation):

  • Plugin must have a complete README and plugin.json
  • MCP servers, if used, must be clearly documented
  • No malicious code or unauthorized data collection
  • Must provide real value, not duplicate existing functionality

Project Links & Resources

Official Resources

Target Audience

  • Power Claude Code users: Looking to extend their workflow capabilities with official plugins
  • Plugin developers: Learning how to build spec-compliant Claude Code plugins
  • Toolchain engineers: Connecting their own services to the Claude Code ecosystem via MCP servers
  • Engineering productivity leads: Building custom team plugins and standardizing development workflows

Summary

Key Takeaways

Plugin ecosystem:

  1. 30+ internal plugins: Covering LSP (12 languages), PR review, Git workflows, code quality, output styles, and other core development scenarios
  2. 15 external plugins: GitHub, Firebase, Linear, Terraform, and other mainstream tools already integrated
  3. Unified installation: One command handles everything — /plugin install <name>@claude-plugins-official

Plugin specification:

  1. Minimal metadata: plugin.json needs only name, description, and author
  2. Three extension mechanisms: Skills (recommended) / Commands (legacy) / MCP Servers (external services)
  3. Two trigger modes: User-invoked vs. model-auto-triggered based on context
  4. Security awareness built in: Official docs explicitly note that Anthropic does not vouch for third-party MCP servers — trust verification is the user’s responsibility

Core value of the five key plugins:

  • pr-review-toolkit: 6 parallel agents → multi-angle coverage, eliminating single-reviewer blind spots
  • agent-sdk-dev: One-command scaffolding + automatic Verifier → lower barrier to entry for Agent SDK
  • code-review: Confidence filtering (default threshold 80) → fewer false positives, focus on real issues
  • hookify: Natural language hook creation → no complex JSON config files
  • commit-commands: /commit-push-pr → full pipeline from code to PR in one command

One-Line Review

claude-plugins-official is not just a plugin directory — it is Anthropic’s public answer to the question “how should AI tools be extended”: minimize metadata, maximize compositional flexibility, let Skills appear in the right context automatically rather than forcing users to memorize commands.

Find more useful knowledge and interesting products on my Homepage

Microsoft Just Published a Blueprint for Self-Healing CI/CD. Here’s What the Observe-Analyze-Act Loop Actually Does.

Pipeline failures are one of those things every engineering team accepts as friction they can’t eliminate — something breaks at 2am, someone gets paged, someone debugs, someone fixes. Microsoft just published a working architecture that removes humans from that first-response loop entirely.

The Problem It’s Solving

Standard CI/CD pipelines fail, send you a stack trace, and wait. One small typo in a backend pool member IP can tank a deployment. The debugging cycle is manual by design: read the logs, understand the context, figure out what broke, push a fix, re-run. For teams migrating infrastructure — legacy load balancer settings to Azure ILB rules, for instance — that cycle can eat days.

The self-healing pipeline architecture Microsoft outlined on the Azure Infrastructure Blog replaces that cycle with an agentic loop. The pipeline still fails. But instead of waiting for a human to read the error, an AI agent reads it, understands it in infrastructure context, and proposes (or executes) a fix.

How It Actually Works

The self-healing workflow is an agentic loop consisting of three phases: Observe, Analyze, and Act. The process begins with an event-driven trigger. When an Azure DevOps pipeline fails, a webhook sends the telemetry and build logs to an Azure Function. The logs are then passed to GPT-4o via the Microsoft AI Foundry endpoint.

That last part is the hinge. The model doesn’t just look for error codes — it understands the infrastructure context. There’s a meaningful difference between a regex that matches “connection refused” and a model that can reason about why a backend pool misconfiguration would produce that error given the surrounding deployment context.

The implementation uses Azure AI Foundry’s ChatCompletionsClient to call GPT-4o with a system prompt that frames it as an autonomous DevOps assistant. The agent receives the raw error logs, analyzes them, and returns a proposed fix. That fix can then trigger a GitHub pull request or an Azure DevOps pipeline update automatically — the “Act” phase closing the loop.

Microsoft AI Foundry provides a standardized way to call Azure OpenAI, which matters for teams that want consistent API surface across environments rather than managing direct OpenAI endpoint configurations per service.

On why GPT-4o specifically: native tool use makes it specifically optimized for function calling, allowing the agent to interact with Azure DevOps APIs and GitHub seamlessly. As a first-party service, Azure OpenAI is also the most cost-effective path to running production-grade agents, and GPT-4o processes complex logs in seconds, identifying errors much faster than a human scanning line by line.

What Teams Are Actually Using This For

The Microsoft post describes a concrete infrastructure migration scenario: mapping legacy load balancer settings — like fastest-app-response or source-address persistence — to Azure ILB rules, where a single typo in backend pool member IPs can tank a deployment.

The agent now scans those configs before the pipeline runs, flags mismatches, and suggests the correct Azure-native equivalent. It’s saved the team days of trial-and-error debugging. That’s the pre-failure application — catching configuration drift before it becomes a deployment failure, not just responding after.

Post-failure, the loop handles anything where the fix is diagnosable from logs alone: dependency mismatches, misconfigured environment variables, failed health checks on newly deployed resources. The agent reads the failure telemetry, identifies the category of error, and proposes a remediation that goes straight to a PR for review — or executes directly, depending on how the “Act” phase is configured.

This connects to a broader pattern Microsoft’s platform engineering teams have been documenting. When a deployment degrades, Argo CD fires a webhook to GitHub Actions, which creates a GitHub issue with the failure details — cluster name, resource group, the initial telemetry. The agent reads the issue, authenticates to Azure via Workload Identity Federation, runs kubectl commands against the affected cluster, and queries the AKS MCP server for deeper telemetry. The self-healing CI/CD architecture is the Azure DevOps-native version of the same idea.

Why This Is a Bigger Deal Than It Looks

The architecture itself isn’t complex — webhook, Azure Function, GPT-4o call, conditional action. What’s significant is that it’s now a documented, first-party pattern from Microsoft’s Azure Infrastructure team, with a real use case attached. That’s different from a proof-of-concept.

AI agents don’t magically fix broken engineering practices — they scale them. If your CI/CD pipelines are fragile, agents will break them faster. If your test coverage is thin, agents will ship untested code at higher velocity. The self-healing architecture assumes your pipeline failures are diagnosable from logs. For teams with well-structured observability, that’s most failures. For teams without it, this pattern will surface the gaps fast.

There’s also a shift in how pipeline failures are categorized. Traditional CI/CD pipelines rely on binary assertions — Assert X == Y. But AI agents are probabilistic. The self-healing loop works well on the deterministic failure surface — config errors, missing dependencies, mismatched API parameters. The harder problem, testing and validating the agent’s own proposed fixes before they ship, is where the architecture gets more complex. For now, the PR-as-output model keeps a human in the loop on the final action, which is the right call for production systems.

By shifting the burden of initial troubleshooting to automated agents, teams aren’t just saving time — they’re increasing the reliability of their entire stack. That framing is accurate, but the reliability gain depends entirely on how the “Act” phase is scoped. Agents that open PRs are recoverable. Agents with direct write access to production pipelines require more careful guardrails before you’d want them running unsupervised.

Availability and Access

The pattern runs on Azure DevOps, Azure Functions, and Azure OpenAI via AI Foundry. No preview program required — these are all generally available services. The full implementation walkthrough, including the ChatCompletionsClient setup and the webhook-to-function wiring, is in the Microsoft Tech Community post.

The architecture is modular enough to adapt: swap Azure Functions for any serverless compute that can receive a webhook, swap GPT-4o for any model with strong function-calling support, and scope the “Act” phase to whatever your organization’s change management policy allows.

The pipeline-as-passive-executor era is ending. Pipelines that can read their own failures, reason about them, and act on them are the next default — and Microsoft just made the blueprint public.

Follow for more coverage on MCP, agentic AI, and AI infrastructure.

De Cero a Kubernetes Parte 5: Dos Frontends + Stack Completo

K8s Terraform ArgoCD — Header

K8s Terraform ArgoCD — Header

Serie: De Cero a Kubernetes — Parte 1 · Parte 2 · Parte 3 · Parte 4 · Parte 5

Llegaste a la última parte. Ya tienes un cluster Kubernetes real, TLS automático, GitOps con ArgoCD, PostgreSQL y un backend FastAPI corriendo con 2 réplicas. Solo falta lo que tus usuarios realmente ven: los frontends.

Vamos a desplegar dos aplicaciones independientes que consumen la misma API. Una es un dashboard administrativo (React + Vite). La otra es una app pública (Vue 3). Ambas empaquetadas con Docker, servidas con nginx para producción, y expuestas con ingress + TLS. Al final, haremos una prueba de integración completa.

Arquitectura final del stack completo

┌────────────────────────────────────────────────────────────┐
│                      INTERNET                               │
│                         │                                   │
│    ┌────────────────────┼────────────────────┐              │
│    │                    │                    │              │
│    ▼                    ▼                    ▼              │
│ ┌──────────┐    ┌──────────────┐    ┌──────────────┐       │
│ │ admin.   │    │ api.         │    │ app.         │       │
│ │tudominio │    │tudominio.com │    │tudominio.com │       │
│ │.com      │    │              │    │              │       │
│ └────┬─────┘    └──────┬───────┘    └──────┬───────┘       │
│      │                 │                   │               │
│      ▼                 ▼                   ▼               │
│ ┌──────────┐    ┌──────────────┐    ┌──────────────┐       │
│ │ React    │    │ FastAPI      │    │ Vue 3        │       │
│ │ Dashboard│───▶│ Backend      │◀───│ Landing Page │       │
│ │ (Vite)   │    │ (2 réplicas) │    │ (Vite)       │       │
│ └──────────┘    └──────┬───────┘    └──────────────┘       │
│                        │                                   │
│                        ▼                                   │
│               ┌────────────────┐                           │
│               │ PostgreSQL 16  │                           │
│               │ (CloudNativePG)│                           │
│               └────────────────┘                           │
│                                                            │
│ ☸️ Kubernetes (kubeadm + containerd + Calico)               │
│ 🌐 nginx ingress + cert-manager (Let's Encrypt TLS)        │
│ 🔄 ArgoCD — todo sincronizado desde GitHub                 │
└────────────────────────────────────────────────────────────┘

Parte A: Frontend #1 — Dashboard Administrativo (React + Vite)

Creamos el proyecto:

npm create vite@latest frontend-admin -- --template react-ts
cd frontend-admin
npm install

Lógica de la app: Un dashboard sencillo que consume GET /items del backend y los muestra en una tabla con estadísticas. Permite crear y eliminar items.

src/App.tsx (simplificado):

import { useEffect, useState } from 'react'

const API_URL = import.meta.env.VITE_API_URL || 'https://api.tudominio.com:30443'

interface Item {
  id: number
  name: string
  description: string
  created_at: string
}

export default function App() {
  const [items, setItems] = useState<Item[]>([])
  const [name, setName] = useState('')
  const [desc, setDesc] = useState('')
  const [loading, setLoading] = useState(true)
  const [dbStatus, setDbStatus] = useState('')

  useEffect(() => {
    fetch(`${API_URL}/health`)
      .then(r => r.json())
      .then(d => setDbStatus(d.db))
    loadItems()
  }, [])

  const loadItems = () => {
    fetch(`${API_URL}/items`)
      .then(r => r.json())
      .then(setItems)
      .finally(() => setLoading(false))
  }

  const createItem = async () => {
    await fetch(`${API_URL}/items`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, description: desc })
    })
    setName('')
    setDesc('')
    loadItems()
  }

  const deleteItem = async (id: number) => {
    await fetch(`${API_URL}/items/${id}`, { method: 'DELETE' })
    loadItems()
  }

  return (
    <div className="dashboard">
      <header>
        <h1>📊 Dashboard Admin</h1>
        <span className={`status ${dbStatus === 'connected' ? 'ok' : 'err'}`}>
          DB: {dbStatus}
        </span>
      </header>

      <section className="create-form">
        <input value={name} onChange={e => setName(e.target.value)}
               placeholder="Nombre del item" />
        <input value={desc} onChange={e => setDesc(e.target.value)}
               placeholder="Descripción (opcional)" />
        <button onClick={createItem}>Crear Item</button>
      </section>

      <section className="items-table">
        <h2>Items ({items.length})</h2>
        {loading ? <p>Cargando...</p> : (
          <table>
            <thead><tr><th>ID</th><th>Nombre</th><th>Descripción</th><th>Creado</th><th></th></tr></thead>
            <tbody>
              {items.map(item => (
                <tr key={item.id}>
                  <td>{item.id}</td>
                  <td>{item.name}</td>
                  <td>{item.description}</td>
                  <td>{new Date(item.created_at).toLocaleString()}</td>
                  <td><button onClick={() => deleteItem(item.id)}>🗑️</button></td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </section>
    </div>
  )
}

.env.production:

VITE_API_URL=https://api.tudominio.com

Parte B: Frontend #2 — Landing Pública (Vue 3)

npm create vite@latest frontend-app -- --template vue-ts
cd frontend-app
npm install

src/App.vue (simplificado):

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const API_URL = import.meta.env.VITE_API_URL || 'https://api.tudominio.com:30443'

const items = ref<any[]>([])
const loading = ref(true)
const form = ref({ name: '', description: '' })

const loadItems = async () => {
  const r = await fetch(`${API_URL}/items`)
  items.value = await r.json()
  loading.value = false
}

const createItem = async () => {
  await fetch(`${API_URL}/items`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(form.value)
  })
  form.value = { name: '', description: '' }
  loadItems()
}

onMounted(loadItems)
</script>

<template>
  <main class="landing">
    <h1>🚀 Guayoyo Items</h1>
    <p class="subtitle">Crea y comparte items al instante</p>

    <form @submit.prevent="createItem" class="create">
      <input v-model="form.name" placeholder="¿Qué quieres crear?" required />
      <textarea v-model="form.description" placeholder="Describe tu item..." rows="2" />
      <button type="submit">Crear</button>
    </form>

    <div class="grid">
      <div v-for="item in items" :key="item.id" class="card">
        <h3>{{ item.name }}</h3>
        <p>{{ item.description }}</p>
        <small>{{ new Date(item.created_at).toLocaleDateString() }}</small>
      </div>
    </div>
    <p v-if="loading">Cargando...</p>
  </main>
</template>

Parte C: Docker + nginx para producción

Ambos frontends usan exactamente el mismo patrón Docker multi-stage:

frontend-admin/Dockerfile (idéntico para ambos, solo cambia el nombre de la app):

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

frontend-admin/nginx.conf:

server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # SPA routing — todas las rutas van a index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache estática
    location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Health check para Kubernetes
    location /health {
        return 200 "OK";
        add_header Content-Type text/plain;
    }
}

Build y push para ambos:

# Admin
docker build -t ghcr.io/tu-org/frontend-admin:v1.0.0 ./frontend-admin
docker push ghcr.io/tu-org/frontend-admin:v1.0.0

# App pública
docker build -t ghcr.io/tu-org/frontend-app:v1.0.0 ./frontend-app
docker push ghcr.io/tu-org/frontend-app:v1.0.0

Parte D: Manifiestos Kubernetes para ambos frontends

Ambos usan la misma estructura. Solo cambian la imagen, el host del ingress y la variable de API.

gitops/apps/frontend-app1/deployment.yaml (Dashboard Admin):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-admin
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend-admin
  template:
    metadata:
      labels:
        app: frontend-admin
    spec:
      containers:
        - name: nginx
          image: ghcr.io/tu-org/frontend-admin:v1.0.0
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "200m"
          livenessProbe:
            httpGet:
              path: /health
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /health
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 10

Service e Ingress para ambos:

# Service (idéntico, cambia name y selector)
apiVersion: v1
kind: Service
metadata:
  name: frontend-admin  # o frontend-app
  namespace: production
spec:
  selector:
    app: frontend-admin
  ports:
    - port: 80
      targetPort: 80
  type: ClusterIP

---
# Ingress (cambia host)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-admin
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-production
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - admin.tudominio.com  # o app.tudominio.com
      secretName: admin-tls
  rules:
    - host: admin.tudominio.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-admin
                port:
                  number: 80

Variables de entorno en build time:

Como estamos sirviendo estáticos con nginx, la URL de la API se inyecta en el build de Vite (VITE_API_URL). Esto significa que API_URL queda hardcodeada en el bundle de JavaScript. Es intencional: no necesitas runtime injection para valores que no cambian entre entornos.

Si quisieras runtime injection sin rebuild, usarías un ConfigMap montado en nginx que exponga un /config.js con window.__CONFIG__. Pero para este tutorial, el build-time injection es suficiente.

Parte E: ArgoCD sincroniza todo

Al hacer git push con los archivos de ambos frontends, ArgoCD detecta los nuevos directorios en gitops/apps/ automáticamente (gracias a directory.recurse: true en app-of-apps). La UI muestra:

🌳 app-of-apps
  ├── 🟢 database        (Healthy · Synced)
  ├── 🟢 backend         (Healthy · Synced)  2 pods
  ├── 🟢 frontend-app1   (Healthy · Synced)  2 pods
  └── 🟢 frontend-app2   (Healthy · Synced)  2 pods

Cuatro aplicaciones, dos réplicas cada una (excepto DB), TLS en todos los ingress. Todo desde un git push.

Parte F: Prueba de integración completa

1. Verificar que todo está verde

argocd app list
# NAME           HEALTH   STATUS
# database       Healthy  Synced
# backend        Healthy  Synced
# frontend-app1  Healthy  Synced
# frontend-app2  Healthy  Synced

2. Crear un item desde la app pública (Vue)

Abrimos https://app.tudominio.com, llenamos el formulario con “Café guayoyo” y creamos. El backend responde 201 Created.

3. Verlo en el dashboard admin (React)

Abrimos https://admin.tudominio.com. La tabla muestra:

ID Nombre Descripción Creado
1 Café guayoyo El mejor café de Caracas 2026-05-20, 10:15:00 AM

El dashboard también muestra DB: connected en verde.

4. Crear otro item desde el dashboard

Botón “Crear Item” → “Tequeños con guasacaca” → Enter.

5. Verificar que la app pública lo ve

Recargamos https://app.tudominio.com. Ahora hay 2 cards:

🚀 Guayoyo Items
Crea y comparte items al instante

┌──────────────────────┐  ┌──────────────────────────┐
│ Tequeños con         │  │ Café guayoyo             │
│ guasacaca            │  │                          │
│ El mejor acompañante │  │ El mejor café de Caracas │
│ 5/20/2026            │  │ 5/20/2026                │
└──────────────────────┘  └──────────────────────────┘

6. Borrar un item desde el dashboard

🗑️ en “Café guayoyo” → desaparece de ambas apps.

Rollback en acción

Imagina que la versión v1.1.0 del backend tiene un bug. Así se revierte:

# 1. Revertir el cambio en git
cd gitops
git revert HEAD --no-edit
git push

# 2. ArgoCD detecta el cambio y revierte el deployment
argocd app diff backend
# (muestra que la imagen volverá a v1.0.0)

# 3. Verificar
curl https://api.tudominio.com/health
# {"status":"ok","db":"connected"}

Tiempo total de rollback: lo que tardes en hacer git revert && git push. Menos de 30 segundos.

Si el rollback de urgencia no puede esperar el reconciliation loop (3 min), fuerzas el sync manual:

argocd app sync backend

Lo que construiste en estas 5 partes

Parte Entregable
Parte 1 3 VMs Hetzner, kubeadm, containerd, Calico — cluster real
Parte 2 Namespaces, nginx ingress, cert-manager (TLS automático), storage, RBAC
Parte 3 ArgoCD, GitOps, app-of-apps, sync automático con selfHeal + prune
Parte 4 PostgreSQL con CloudNativePG, backend FastAPI con health checks y migraciones
Parte 5 Dos frontends (React + Vue, cada uno en su ingress con TLS), comunicación backend ↔ ambos

Stack tecnológico completo: Hetzner Cloud → Terraform → kubeadm → containerd → Calico → nginx ingress → cert-manager → ArgoCD → CloudNativePG → FastAPI → React → Vue → Docker → GitHub

Próximos pasos (por tu cuenta)

Lo que construiste es la base. Sobre esto puedes agregar:

  • CI con GitHub Actions: Build de Docker automático en cada push, push a registry, actualizar tag en repo GitOps
  • Monitoreo: Prometheus + Grafana (el operator kube-prometheus-stack se instala en 5 minutos)
  • Logs centralizados: Loki + Grafana (o Elastic Cloud on Kubernetes)
  • HA: Segunda instancia de PostgreSQL con replicación, múltiples control-planes
  • Service Mesh: Linkerd para mTLS automático entre servicios
  • Image Updater: ArgoCD Image Updater para auto-actualizar imágenes cuando hay nuevo tag
  • Secrets de verdad: Migrar de Secret en texto plano a SealedSecrets o ExternalSecrets + Vault

En Guayoyo Tech armamos arquitecturas completas como esta para empresas que necesitan infraestructura seria. Desde el Terraform que levanta las VMs hasta el último botón del dashboard React. Kubernetes, GitOps, CI/CD, monitoreo — todo integrado, documentado y con transferencia de conocimiento incluida. Habla con el equipo gratis 15 minutos y te mostramos cómo llevamos tu infraestructura al siguiente nivel.

De Cero a Kubernetes Parte 3: ArgoCD, GitOps y App-of-Apps

K8s Terraform ArgoCD — Header

K8s Terraform ArgoCD — Header

Serie: De Cero a Kubernetes — Parte 1 · Parte 2 · Parte 3 · Parte 4 · Parte 5

Hasta ahora construimos la plataforma con Terraform. Pero Terraform no es un CD pipeline — correr terraform apply cada vez que cambia una imagen de contenedor es una tortura. Aquí entra ArgoCD.

ArgoCD implementa GitOps: el estado deseado de tu cluster vive en un repositorio de git. ArgoCD monitorea ese repo y garantiza que el cluster refleje exactamente lo que está commiteado. Si alguien hace kubectl edit manual, ArgoCD lo revierte. Si un deploy falla, ArgoCD te dice exactamente qué recurso está roto y por qué.

GitOps en 30 segundos

┌──────────┐   git push    ┌──────────┐
│  GitHub   │◄──────────────│  Tú       │
│  (repo)   │               └──────────┘
└────┬─────┘
     │ ArgoCD lee el repo cada 3 min
     ▼
┌──────────┐    sync    ┌──────────────────┐
│ ArgoCD   │───────────►│  k8s cluster      │
│ (en el   │◄───────────│  (tus apps)       │
│ cluster) │   status   └──────────────────┘
     │
     ▼  (UI opcional en argocd.tudominio.com)
┌──────────────┐
│  🌳 Árbol de │ → ves el diff, health, logs
│     apps      │ → sync manual o automático
│               │ → rollback con revertir commit
└──────────────┘

Las tres propiedades que diferencian GitOps de un script de deploy:

  1. Reconciliation loop: Cada 3 minutos ArgoCD compara el cluster contra git. Si hay drift, lo corrige.
  2. Self-healing: Alguien borró un Deployment por error → ArgoCD lo recrea.
  3. Auditabilidad: Cada cambio al cluster tiene un commit asociado. Sabes quién, qué, cuándo y por qué.

Paso 1: Instalar ArgoCD con Terraform

resource "helm_release" "argocd" {
  name       = "argocd"
  namespace  = kubernetes_namespace.argocd.metadata[0].name
  repository = "https://argoproj.github.io/argo-helm"
  chart      = "argo-cd"
  version    = "7.6.7"

  set {
    name  = "server.service.type"
    value = "ClusterIP"  # Lo exponemos vía ingress, no NodePort
  }

  set {
    name  = "server.ingress.enabled"
    value = "true"
  }

  set {
    name  = "server.ingress.hosts[0]"
    value = "argocd.tudominio.com"
  }

  set {
    name  = "server.ingress.annotations.nginx\.ingress\.kubernetes\.io/ssl-redirect"
    value = "true"
  }

  set {
    name  = "server.ingress.annotations.cert-manager\.io/cluster-issuer"
    value = "letsencrypt-production"
  }

  set {
    name  = "server.ingress.tls[0].hosts[0]"
    value = "argocd.tudominio.com"
  }

  set {
    name  = "server.ingress.tls[0].secretName"
    value = "argocd-tls"
  }

  # Desactivamos TLS interno ya que el ingress maneja TLS externo
  set {
    name  = "server.extraArgs[0]"
    value = "--insecure"
  }

  depends_on = [helm_release.cert_manager]
}

Accedes a la UI en https://argocd.tudominio.com. La contraseña inicial de admin:

kubectl -n argocd get secret argocd-initial-admin-secret 
  -o jsonpath="{.data.password}" | base64 -d

Paso 2: La contraseña admin como código (buena práctica)

No queremos passwords generados aleatoriamente en cada terraform apply. Usamos bcrypt:

# Generas el hash una vez:
# htpasswd -nbBC 10 "" "tu-password-segura" | tr -d ':n'

resource "kubernetes_secret" "argocd_admin_password" {
  metadata {
    name      = "argocd-secret"
    namespace = "argocd"
  }

  data = {
    "admin.password"   = "$2a$10$..." # hash bcrypt
    "admin.passwordMtime" = "2026-05-20T00:00:00Z"
  }
}

Paso 3: Estructura del repositorio GitOps

Creamos un repo separado (o un directorio en el monorepo) con esta estructura:

gitops/
├── apps/
│   ├── backend/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── ingress.yaml
│   │   ├── configmap.yaml
│   │   └── kustomization.yaml
│   ├── frontend-app1/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── ingress.yaml
│   │   ├── configmap.yaml
│   │   └── kustomization.yaml
│   ├── frontend-app2/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── ingress.yaml
│   │   ├── configmap.yaml
│   │   └── kustomization.yaml
│   └── database/
│       ├── postgresql-cluster.yaml
│       └── kustomization.yaml
└── clusters/
    └── production/
        └── app-of-apps.yaml    ← LA LLAVE MAESTRA

Paso 4: App-of-Apps — el patrón que lo escala todo

En vez de crear 15 Applications manualmente en ArgoCD, creas una Application raíz que apunta a apps/. Cada subdirectorio se convierte automáticamente en una Application. Añadir una app nueva es crear una carpeta. Sin tocar ArgoCD.

clusters/production/app-of-apps.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-of-apps
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/tu-org/gitops-repo
    targetRevision: main
    path: apps
    directory:
      recurse: true
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true      # borra recursos que eliminaste del repo
      selfHeal: true   # revierte cambios manuales en el cluster
    syncOptions:
      - CreateNamespace=true

Las 3 banderas que importan:

  • prune: true → Si borras apps/backend/deployment.yaml del repo, ArgoCD borra el Deployment del cluster. Sin esto, el recurso queda huérfano para siempre.
  • selfHeal: true → Si alguien hace kubectl edit deployment backend y cambia réplicas de 2 a 5, ArgoCD lo revierte a 2 en el siguiente reconciliation (≤3 min).
  • CreateNamespace=true → Si el namespace no existe, lo crea. Evita el error de “namespace not found”.

Paso 5: Registrar el repo en ArgoCD

ArgoCD necesita acceso de lectura al repo. Usamos un token de GitHub con scope repo:

kubectl create secret generic github-repo-creds 
  -n argocd 
  --from-literal=url=https://github.com/tu-org/gitops-repo 
  --from-literal=username=gitops-bot 
  --from-literal=password=ghp_tuTokenDeGitHub 
  --from-literal=type=git 
  --dry-run=client -o yaml | kubectl apply -f -

Y etiquetamos el secret para que ArgoCD lo use:

kubectl label secret github-repo-creds -n argocd 
  argocd.argoproj.io/secret-type=repository

Paso 6: Crear la Application raíz

La app-of-apps se crea una sola vez. Puedes hacerlo con Terraform o directamente:

resource "kubernetes_manifest" "app_of_apps" {
  manifest = {
    apiVersion = "argoproj.io/v1alpha1"
    kind       = "Application"
    metadata = {
      name      = "app-of-apps"
      namespace = "argocd"
    }
    spec = {
      project = "default"
      source = {
        repoURL        = "https://github.com/tu-org/gitops-repo"
        targetRevision = "main"
        path           = "apps"
        directory = {
          recurse = true
        }
      }
      destination = {
        server    = "https://kubernetes.default.svc"
        namespace = "argocd"
      }
      syncPolicy = {
        automated = {
          prune    = true
          selfHeal = true
        }
        syncOptions = ["CreateNamespace=true"]
      }
    }
  }
}

Paso 7: Ver el árbol de apps

A los pocos segundos de aplicar, la UI de ArgoCD muestra:

🌳 app-of-apps
  ├── 🟢 backend        (Healthy · Synced)
  ├── 🟢 frontend-app1  (Healthy · Synced)
  ├── 🟢 frontend-app2  (Healthy · Synced)
  └── 🟢 database       (Healthy · Synced)

Los círculos verdes indican que el recurso está Healthy (pods corriendo, probes pasando) y Synced (el cluster coincide con git).

Si algo falla:

  • Amarillo (Progressing): Pods arrancando, aún no pasan health checks.
  • Rojo (Degraded): CrashLoopBackOff, ImagePullBackOff, probes fallando.
  • OutOfSync: Alguien cambió algo manualmente → ArgoCD lo marcará para corrección o lo corregirá solo (selfHeal).

Paso 8: ArgoCD CLI — para los que prefieren terminal

# Instalar
brew install argocd

# Login
argocd login argocd.tudominio.com --username admin --password ...

# Listar apps
argocd app list

# Ver diff entre git y cluster
argocd app diff backend

# Sincronizar manualmente (si no usas auto-sync)
argocd app sync backend

# Rollback (revertir el commit en git es mejor)
argocd app rollback backend

# Ver logs de un pod específico sin kubectl
argocd app logs backend --tail=50

También hay una extensión de VS Code que muestra el estado de ArgoCD en el editor. Muy útil cuando estás codeando y quieres ver si el deploy pasó sin cambiar de ventana.

El flujo de trabajo diario con GitOps

1. Cambias algo en el código de la app (main.py, App.tsx, etc.)
2. Build de Docker → nueva imagen (ghcr.io/tu-org/backend:v1.2.3)
3. Actualizas gitops/apps/backend/deployment.yaml:
      image: ghcr.io/tu-org/backend:v1.2.3
4. git add && git commit && git push
5. ArgoCD detecta el cambio → sync automático
6. Si algo falla → rollback: git revert && git push

Todo el historial de deploys es el historial de git. Todos los rollbacks son git revert. No hay “procedimiento de rollback” — es el mismo procedimiento que cualquier cambio de código.

Qué aprendiste en esta parte

  • GitOps como filosofía: git es la fuente única de verdad del cluster
  • ArgoCD instalado con Terraform, expuesto vía ingress con TLS
  • El patrón app-of-apps: una Application raíz que gestiona N aplicaciones
  • syncPolicy: prune + selfHeal + auto-sync para deployments automáticos
  • Cómo ArgoCD detecta drift y lo corrige sin intervención humana
  • ArgoCD CLI y UI para debugging
  • El flujo completo: git push → ArgoCD sync → deploy en segundos

En la Parte 4, desplegaremos la base de datos PostgreSQL y el backend API. Con código real, health checks, migraciones automáticas y secretos gestionados correctamente.

En Guayoyo Tech implementamos GitOps en equipos que aún están haciendo deploys manuales. ArgoCD, Flux, pipelines CI/CD — te ayudamos a pasar de “funciona en mi máquina” a “está en producción y se corrige solo”. Habla con nosotros gratis 15 minutos y te contamos cómo.

Expo Router vs React Navigation – Which One Should You Use in 2026?

TL;DR (2026 Verdict): For most new React Native projects—especially those using Expo—choose Expo Router. It delivers superior developer experience, automatic deep linking, file-based routing that scales with teams, and excellent web parity with minimal boilerplate.

React Navigation remains the right choice for brownfield native integrations, highly bespoke custom transitions, or when you need maximum low-level control outside the Expo ecosystem. Expo Router is built on top of React Navigation, so you’re not abandoning its power—you’re gaining a smarter convention layer.

As a Senior Mobile Solutions Architect who’s shipped dozens of production React Native apps, here’s my practical, battle-tested comparison.

1. The Evolution: Routing as State Management for Screens

Routing in mobile apps is state management for screens: it decides which screen is visible, manages the navigation stack/history, handles transitions, deep links, and preserves state as users move around.

React Navigation (the OG) gave us a robust, flexible imperative/config-based system. It became the de facto standard because it worked reliably across complex flows and gave fine-grained control over navigators (Stack, Tab, Drawer, etc.).

Expo Router is the modern evolution. Introduced to solve real pain points in large codebases, it builds directly on React Navigation while adding file-based routing, automatic deep linking, TypeScript inference, and universal (web + native) capabilities. In 2026, it’s mature, production-proven, and the default recommendation for most new apps.

2. The Paradigm Shift: Imperative Configuration vs. Declarative File-Based Routing

React Navigation (Imperative/Configuration-based):

  • You manually define navigators and register screens in code (often a central App.tsx or spread across modules).
  • Adding a screen? Update the navigator config + import the component.
  • Nested navigation requires explicit nesting of navigators.

Expo Router (Declarative/File-based):

  • Your folder structure is your routes. Create a file → it becomes a route.
  • _layout.tsx files define the navigator type and shared UI for that segment.

Example Folder Structure (The Lightbulb Moment):

/app
  ├── _layout.tsx          # Root layout (Providers, Theme, Auth guard)
  ├── (auth)               # Route group (no URL segment)
  │   ├── _layout.tsx      # Auth Stack
  │   ├── login.tsx        # /login
  │   └── register.tsx     # /register
  ├── (tabs)               # Tab group
  │   ├── _layout.tsx      # Bottom Tabs config
  │   ├── index.tsx        # Home (/)
  │   └── profile.tsx      # /profile
  └── [user].tsx           # Dynamic: /bhupesh_joshi

This maps directly to navigation. No massive config file. New team members can read the folder and understand the app structure instantly.

3. The ‘Deep Linking’ Argument: Web/Mobile Parity Wins

Expo Router treats every route as deep-linkable by default. No separate linking config hell. This shines for:

  • Universal apps (same code for mobile + web)
  • Shareable content
  • SEO on web (static rendering, meta tags)
  • Universal Links / App Links

React Navigation requires manual setup for deep linking and has weaker web support. In 2026, with more teams building universal experiences, this is a major advantage.

4. Technical Deep Dive

Protected Routes & Authentication

Expo Router (Clean):

  • Use redirect in _layout.tsx with useRootNavigationState or a custom hook.
  • Slot pattern for layouts.
  • Auth state lives high; redirects are declarative.

React Navigation:

  • Conditional rendering of different root navigators (AuthStack vs AppStack) based on auth state.
  • Works well but leads to more imperative logic and potential state synchronization issues.

Performance & Bundle Behavior

  • Expo Router: Automatic route-based bundle splitting (lazy loading via Suspense), deferred bundling. Smaller initial bundles, especially beneficial on web. Navigation transitions remain native and smooth (powered by React Navigation + react-native-screens).
  • React Navigation: Excellent runtime performance. You manage bundling yourself. In large apps, the central config can bloat the main bundle if not careful.

Developer Workflow: Expo Router wins decisively. No hunting through a giant navigator file. Changes are localized to files/folders. Tooling (TypeScript routes, auto-completion) is superior.

5. When to Stick with React Navigation

Don’t migrate existing stable apps just for the hype. Stick with (or choose) React Navigation when:

  • Highly bespoke navigation transitions or gestures that push Expo Router’s conventions.
  • Brownfield integrations — adding React Native to an existing native app where you control the native navigation layer tightly.
  • Apps that cannot (or prefer not to) use the Expo SDK/monorepo setup.
  • Maximum flexibility with non-serializable data passing or deeply custom navigator compositions.
  • Teams with heavy existing investment in custom React Navigation utilities.

Final Verdict Table (2026)

Aspect Expo Router React Navigation Winner
DX / Boilerplate Excellent (file-based) Good (more config) Expo Router
Scalability (Teams) Superior (self-documenting structure) Good (explicit but verbose) Expo Router
Web Support / Deep Linking First-class, automatic Manual, more work Expo Router
Performance Great + bundle splitting Excellent native Tie (contextual)
Customization Good (falls back to RN APIs) Maximum control React Nav
Learning Curve Gentle for web devs Familiar for RN veterans Depends
Enterprise Maintainability High (conventions) High (explicit) Expo Router (new)

Real-World Advice from the Trenches

  • New Greenfield Project in Expo? → Expo Router. The productivity gains compound.
  • Large Team/Dashboard App with Auth + Tabs + Nested Stacks? → Expo Router enforces clean boundaries.
  • Complex Animations or Native Module Heavy? → Evaluate both, but lean React Navigation if conventions feel restrictive.
  • Production Adoption: Many teams have successfully migrated 60k+ MAU apps. Others run hybrid approaches.

Mental Model Shift: Stop thinking “I need to register this screen.” Start thinking “Where does this screen live in the URL/app hierarchy?”

Expo Router doesn’t replace React Navigation—it elevates it with conventions that make teams faster and code more maintainable. In 2026, for the majority of production-grade apps, that’s the winning approach.

Choose based on your constraints, not hype. But if you’re starting fresh, Expo Router is the 2026 default.

Build something great.

Google Just Rebuilt Its Enterprise AI Stack at I/O ’26. Here’s What Gemini 3.5, Spark, and Antigravity Actually Do.

Google I/O ’26 dropped today, and for the first time in a while, the enterprise announcements are the ones worth paying attention to. Not because of model benchmarks — though those are interesting — but because Google just shipped an integrated agentic stack that reaches from the model layer all the way down to the individual worker’s inbox.

The Problem It’s Solving

Enterprise AI has had a deployment problem. Most organizations have access to capable models, but the path from “we have Gemini” to “our teams are actually running less manual work” has involved a lot of custom integration, fragile automations, and agents that can’t see across tools. What Google is trying to do with this I/O release is close that gap — ship the plumbing, not just the model.

The announcement covers five distinct products: Gemini 3.5, Gemini Omni, Google Antigravity, Gemini Spark, and a Managed Agents API on Agent Platform. Each one sits at a different layer of the stack.

How It Actually Works

Start with the model. Gemini 3.5 Flash is the new baseline — Google’s claim is that it rivals larger flagship models while staying within Flash’s speed and cost profile. The numbers they’re citing: 76.2% on Terminal-Bench 2.1, 83.6% on MCP Atlas, and 84.2% on CharXiv for multimodal understanding. Gemini 3.5 Pro is in testing and coming next month.

That MCP Atlas benchmark is worth noting specifically. Google scored Gemini 3.5 Flash against a benchmark designed around Model Context Protocol task completion — the same protocol that’s become the de facto standard for tool-using agents across the industry. Getting 83.6% there isn’t just a number; it’s a signal about where Google thinks the evaluation bar for agentic models should be.

Gemini Omni is the video-first model — takes text, audio, image, and video inputs and produces dynamic video output. Think post-production automation, e-commerce virtual try-ons, content localization. It’s rolling out in the coming weeks via the Gemini API.

Antigravity 2.0 is where things get more interesting for developers. It’s a standalone desktop app and now integrates with Agent Platform, meaning it inherits Google Cloud’s data privacy protections by default. There’s also an Antigravity CLI for teams that want a lighter-weight interface. The pitch from AirAsia Next’s CTO: over half of their production-ready code now comes through Antigravity agentic workflows. That’s a real number from a shipping company, not a demo.

Gemini Spark is the personal agent layer. It runs 24/7 in the background, connects to Workspace plus external connectors like Salesforce, Zendesk, ServiceNow, and SharePoint, and can take multi-step actions autonomously — with approval gates for anything high-risk. Every task runs in an ephemeral VM, credentials never touch the agent directly, and all traffic routes through an Agent Gateway that enforces DLP policies. The isolation story is more specific than most personal agent announcements tend to be.

The Managed Agents API lets developers spin up custom agents via a single API call, running in Google-hosted environments. No infrastructure to manage; governance and security inherit from Agent Platform automatically.

And there’s CodeMender — an AI security agent from Google DeepMind, now integrated into Agent Platform. It finds vulnerabilities, proposes patches, tests them, and can apply fixes across dependent systems with developer approval.

What Developers and Enterprises Are Actually Using This For

The use cases Google is demonstrating are specific enough to be useful as a map.

For IT operations: Spark monitors ServiceNow, detects recurring incidents, creates escalated Jira tickets, drafts incident reports, and pings the right manager for approval before sending anything externally.

For sales: Spark pulls account history from Salesforce, cross-references support tickets from Zendesk, identifies churn signals, and drafts a retention strategy — sitting in draft until the salesperson approves it.

For product launches: Antigravity 2.0 handles simultaneous agent-driven execution across code generation, asset creation, and customer email drafts, all orchestrated from a single workspace.

For security: CodeMender audits codebases, recommends patches, and deploys them with human sign-off. This is particularly relevant for teams carrying compliance obligations where every change needs an audit trail.

Why This Is a Bigger Deal Than It Looks

The piece that matters most here isn’t any single product — it’s that Google is shipping an end-to-end agentic stack with enterprise data controls built in from the start, not bolted on.

Most enterprise AI deployments today involve stitching together a model API, a separate orchestration layer, custom connector work, and some homegrown governance layer. Google is trying to collapse that into a single platform surface where the governance, security, and agent behavior are codesigned. The Managed Agents API making Agent Platform’s data protections automatic is a specific example of what that looks like in practice.

The MCP Atlas benchmark score is also a tell. Scoring Gemini 3.5 Flash against an MCP-specific benchmark is an implicit endorsement of MCP as the standard evaluation surface for agentic capability — significant given how much momentum MCP has built across the industry since Google Cloud Next ’26.

Availability and Access

Gemini 3.5 Flash is live today in Gemini Enterprise, Google AI Studio, and Antigravity. Gemini Omni Flash comes in the next few weeks. Gemini Spark in the Gemini Enterprise app is rolling out soon; Workspace preview for business customers follows. Antigravity in Gemini Enterprise arrives in the coming months. Managed Agents API documentation is live at docs.cloud.google.com.

Gemini 3.5 Pro remains in testing, expected next month.

The shift from AI-assisted work to AI-executed work — with humans approving rather than doing — is the actual direction this points. Google’s bet at I/O ’26 is that enterprises will adopt that model faster if the security and governance story is tight from day one, not something they have to build themselves.

Follow for more coverage on MCP, agentic AI, and AI infrastructure.

De Cero a Kubernetes Parte 4: Backend FastAPI + PostgreSQL en Producción

K8s Terraform ArgoCD — Header

K8s Terraform ArgoCD — Header

Serie: De Cero a Kubernetes — Parte 1 · Parte 2 · Parte 3 · Parte 4 · Parte 5

Ya tenemos un cluster con ingress, TLS y GitOps. Ahora le ponemos carga de trabajo real: una base de datos PostgreSQL gestionada por un operador y un backend en FastAPI que se conecta a ella, ejecuta migraciones al arrancar, y expone una API REST con health checks.

Esta es la parte donde el tutorial deja de ser “miren qué bonito el cluster vacío” y empieza a ser “esto ya funciona en producción”.

Componentes de esta parte

┌─────────────────────────────────────────┐
│          api.tudominio.com               │
│                │                         │
│    ┌───────────▼───────────┐             │
│    │   Ingress             │             │
│    │   (nginx + TLS)       │             │
│    └───────────┬───────────┘             │
│                │                         │
│    ┌───────────▼───────────┐             │
│    │   Backend Service     │             │
│    │   (ClusterIP)         │             │
│    └───────────┬───────────┘             │
│                │                         │
│    ┌───────────▼───────────┐             │
│    │   Backend Deployment  │             │
│    │   2 réplicas          │             │
│    │   FastAPI + Uvicorn   │             │
│    │   Init container:     │             │
│    │   migraciones         │             │
│    └───────────┬───────────┘             │
│                │                         │
│    ┌───────────▼───────────┐             │
│    │   PostgreSQL          │             │
│    │   (CloudNativePG)     │             │
│    │   1 instancia         │             │
│    │   PVC 10Gi            │             │
│    └───────────────────────┘             │
└─────────────────────────────────────────┘

Paso 1: CloudNativePG — PostgreSQL como recurso nativo de Kubernetes

CloudNativePG (CNPG) es un operador que gestiona PostgreSQL en Kubernetes sin StatefulSets manuales. Lo instalas y creas un recurso Cluster. El operador maneja replicación, backups, failover y actualizaciones. Es la forma moderna de correr PostgreSQL en Kubernetes.

Instalamos el operador con Terraform (Parte 2, pero lo documentamos aquí):

resource "helm_release" "cnpg" {
  name       = "cloudnative-pg"
  namespace  = kubernetes_namespace.infrastructure.metadata[0].name
  repository = "https://cloudnative-pg.github.io/charts"
  chart      = "cloudnative-pg"
  version    = "0.22.0"
}

Luego definimos la base de datos como código en nuestro repo GitOps:

gitops/apps/database/postgresql-cluster.yaml:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: guayoyo-db
  namespace: production
spec:
  instances: 1
  imageName: ghcr.io/cloudnative-pg/postgresql:16.4

  bootstrap:
    initdb:
      database: guayoyo
      owner: app
      secret:
        name: db-credentials

  storage:
    size: 10Gi
    storageClass: local-path

  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
    limits:
      memory: "512Mi"
      cpu: "500m"

  monitoring:
    enablePodMonitor: false

gitops/apps/database/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
  - postgresql-cluster.yaml
  - db-credentials-secret.yaml

Paso 2: Secretos — sin texto plano en git

gitops/apps/database/db-credentials-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: kubernetes.io/basic-auth
stringData:
  username: app
  password: "CambiaEstoEnProduccionConSealedSecrets"

⚠️ En producción real: Nunca pongas passwords en texto plano en git. Usa una de estas opciones:

  1. SealedSecrets — encripta el Secret completo; solo el controlador en el cluster sabe descifrarlo. El YAML encriptado sí puede committearse.
  2. ExternalSecrets — sincroniza secretos desde Vault, AWS Secrets Manager o 1Password.
  3. SOPS + Age — encripta archivos completos con llaves age.

Para este tutorial, el Secret va en texto plano PORQUE es un cluster de laboratorio. Pero quiero que quede claro: en tu empresa, esto jamás va así.

Paso 3: Código del backend (FastAPI)

Creamos el backend en un repo separado (o subdirectorio del monorepo). Estructura:

backend/
├── Dockerfile
├── requirements.txt
├── main.py
├── models.py
├── database.py
└── migrations/
    └── 001_init.sql

main.py:

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import asyncpg
import os
import time
from contextlib import asynccontextmanager

DB_HOST = os.getenv("DB_HOST", "guayoyo-db-rw")
DB_PORT = os.getenv("DB_PORT", "5432")
DB_NAME = os.getenv("DB_NAME", "guayoyo")
DB_USER = os.getenv("DB_USER", "app")
DB_PASSWORD = os.getenv("DB_PASSWORD", "")

pool = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global pool
    max_retries = 10
    for attempt in range(max_retries):
        try:
            pool = await asyncpg.create_pool(
                host=DB_HOST,
                port=DB_PORT,
                database=DB_NAME,
                user=DB_USER,
                password=DB_PASSWORD,
                min_size=2,
                max_size=10
            )
            # Inicializar tablas
            async with pool.acquire() as conn:
                await conn.execute("""
                    CREATE TABLE IF NOT EXISTS items (
                        id SERIAL PRIMARY KEY,
                        name TEXT NOT NULL,
                        description TEXT,
                        created_at TIMESTAMPTZ DEFAULT NOW()
                    )
                """)
            break
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(3)
    yield
    await pool.close()

app = FastAPI(title="Guayoyo API", lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://admin.tudominio.com",
        "https://app.tudominio.com",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class ItemCreate(BaseModel):
    name: str
    description: str = ""

class Item(ItemCreate):
    id: int
    created_at: str

@app.get("/health")
async def health():
    try:
        async with pool.acquire() as conn:
            await conn.fetchval("SELECT 1")
        return {"status": "ok", "db": "connected", "timestamp": time.time()}
    except Exception as e:
        return {"status": "degraded", "db": str(e)}

@app.post("/items", response_model=Item)
async def create_item(item: ItemCreate):
    async with pool.acquire() as conn:
        row = await conn.fetchrow(
            "INSERT INTO items (name, description) VALUES ($1, $2) RETURNING *",
            item.name, item.description
        )
    return Item(id=row["id"], name=row["name"],
               description=row["description"] or "",
               created_at=str(row["created_at"]))

@app.get("/items")
async def list_items():
    async with pool.acquire() as conn:
        rows = await conn.fetch("SELECT * FROM items ORDER BY created_at DESC")
    return [{"id": r["id"], "name": r["name"],
             "description": r["description"] or "",
             "created_at": str(r["created_at"])} for r in rows]

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    async with pool.acquire() as conn:
        row = await conn.fetchrow("SELECT * FROM items WHERE id = $1", item_id)
    if not row:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"id": row["id"], "name": row["name"],
            "description": row["description"] or "",
            "created_at": str(row["created_at"])}

@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    async with pool.acquire() as conn:
        result = await conn.execute("DELETE FROM items WHERE id = $1", item_id)
    if result == "DELETE 0":
        raise HTTPException(status_code=404, detail="Item not found")
    return {"status": "deleted"}

Dockerfile:

FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --no-cache-dir poetry
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create false && poetry install --no-dev

FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
EXPOSE 8000
HEALTHCHECK --interval=10s --timeout=5s --retries=3 
  CMD curl -f http://localhost:8000/health || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Build y push:

docker build -t ghcr.io/tu-org/backend:v1.0.0 .
docker push ghcr.io/tu-org/backend:v1.0.0

Paso 4: Desplegar en Kubernetes vía ArgoCD

gitops/apps/backend/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      serviceAccountName: apps-sa
      containers:
        - name: backend
          image: ghcr.io/tu-org/backend:v1.0.0
          ports:
            - containerPort: 8000
          env:
            - name: DB_HOST
              value: guayoyo-db-rw.production.svc.cluster.local
            - name: DB_PORT
              value: "5432"
            - name: DB_NAME
              value: guayoyo
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10

Puntos importantes aquí:

  • guayoyo-db-rw.production.svc.cluster.local — este es el DNS interno que CloudNativePG crea automáticamente. -rw = read-write (la primaria). Si tuvieras réplicas de lectura, estarían en -ro.
  • secretKeyRef inyecta username/password del Secret db-credentials sin exponerlos en el Deployment
  • livenessProbe y readinessProbe — Kubernetes usa estos para saber si el pod está vivo y si debe recibir tráfico. Si /health falla, Kubernetes reinicia el pod (liveness) o lo saca del balanceador (readiness)

gitops/apps/backend/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: production
spec:
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8000
      protocol: TCP
  type: ClusterIP

gitops/apps/backend/ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend
  namespace: production
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-production
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.tudominio.com
      secretName: api-tls
  rules:
    - host: api.tudominio.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 80

gitops/apps/backend/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
  - deployment.yaml
  - service.yaml
  - ingress.yaml

Paso 5: Verificación end-to-end

# 1. Verificar que ArgoCD sincronizó todo
argocd app list
# backend  Healthy  Synced
# database Healthy  Synced

# 2. Probar la API
curl https://api.tudominio.com:30443/health
# {"status":"ok","db":"connected","timestamp":1716163200.123}

# 3. Crear un item
curl -X POST https://api.tudominio.com:30443/items 
  -H "Content-Type: application/json" 
  -d '{"name":"Test Item","description":"Desde curl"}'
# {"id":1,"name":"Test Item","description":"Desde curl","created_at":"..."}

# 4. Listar items
curl https://api.tudominio.com:30443/items
# [{"id":1,"name":"Test Item",...}]

# 5. Borrar un item
curl -X DELETE https://api.tudominio.com:30443/items/1
# {"status":"deleted"}

# 6. Verificar que las 2 réplicas están corriendo
kubectl get pods -n production -l app=backend
# NAME                      READY   STATUS    RESTARTS   AGE
# backend-xxxxx-xxxxx       1/1     Running   0          1m
# backend-xxxxx-yyyyy       1/1     Running   0          1m

Qué aprendiste en esta parte

  • CloudNativePG como operador de PostgreSQL en Kubernetes
  • DNS interno de Kubernetes: guayoyo-db-rw.production.svc.cluster.local
  • Secretos para credenciales (y la nota seria sobre SealedSecrets/ExternalSecrets)
  • FastAPI con connection pooling a PostgreSQL usando asyncpg
  • Init container pattern para migraciones al arranque
  • Liveness y readiness probes — la diferencia y por qué ambas importan
  • CORS configurado para aceptar tráfico de los frontends
  • Construcción de imagen Docker multi-stage optimizada

En la Parte 5, desplegaremos los dos frontends (React + Vue), los conectaremos al backend, y haremos una prueba de integración completa donde una app crea datos y la otra los consume en tiempo real.

En Guayoyo Tech diseñamos backends que no se caen cuando hay tráfico real. FastAPI, Go, Node.js — lo que tu proyecto necesite, con PostgreSQL, health checks, métricas y deployment automatizado en Kubernetes. Hablemos gratis 15 minutos y te mostramos cómo.