Z3 Can Prove Your Cloud is Unsafe. It Can’t Tell You Why.

Z3 is one of the most powerful reasoning engines ever built. Microsoft Research created it to verify chip designs and flight software. It can take your cloud configuration, model it as a set of logical assertions, and mathematically prove whether an attack path exists.

Z3 says when it finds one:

sat

Three letters. No context. No explanation. No link to the configuration property that caused it. No fix. Just “sat” — which means “satisfiable,” which means “the unsafe state you asked about is reachable,” which means your cloud is vulnerable. Probably. If you encoded the question correctly. Which you can’t verify from the output.

This is the gap between a powerful engine and a useful tool. The engine answers the question. The tool explains the answer. This article explains why the explanation layer matters more than the engine, and what it looks like in practice.

What Z3 outputs

Let’s say you want to check whether an anonymous internet user can read PHI data from an S3 bucket through a Cognito identity pool. You model the configuration as SMT-LIB assertions, write a query, and run it through Z3.

The input Z3 sees:

(set-logic ALL)
(declare-fun allows_unauthenticated (String String) Bool)
(declare-fun maps_unauth_to (String String) Bool)
(declare-fun has_action (String String) Bool)
(declare-fun has_tag (String String) Bool)

(assert (allows_unauthenticated "pool-abc" "true"))
(assert (maps_unauth_to "pool-abc" "role/AppUnauthRole"))
(assert (has_action "role/AppUnauthRole" "s3:GetObject"))
(assert (has_tag "bucket-prod-phi" "data_classification:phi"))

(declare-const principal String)
(declare-const action String)
(declare-const resource String)

(assert (allows_unauthenticated principal "true"))
(assert (has_action principal action))
(assert (has_tag resource "data_classification:phi"))

(check-sat)
(get-model)

The output Z3 produces:

sat
(
  (define-fun principal () String "pool-abc")
  (define-fun action () String "s3:GetObject")
  (define-fun resource () String "bucket-prod-phi")
)

In SMT-LIB, sat means the forbidden state is reachable. The model names the specific pool, action, and bucket. The proof is mathematically sound.

If you’re a security engineer who just wants to know whether your Cognito configuration is safe, this output is useless. You have three questions:

  1. Is this result correct? Did the input assertions match your configuration, or did the translation introduce a bug?
  2. What does it mean? Which specific settings in which specific files create the vulnerability?
  3. What do I do about it? What’s the fix, what does it cost, and how do I verify it worked?

Z3 answers none of these. It answered the math question. The security question is still open.

The two translation boundaries

Between your cloud configuration and Z3’s answer, there are two translation steps. Each can introduce bugs. Each is invisible if you only look at the solver’s output.

YOUR CLOUD CONFIG          THE MATH              YOUR ANSWER

  S3 bucket policy    →    SMT-LIB assertions    →    "sat"
  IAM role policy          (was the translation        (was the translation
  Cognito settings          correct?)                   back to cloud
                                                        terms correct?)

  ENCODING BOUNDARY        Z3 SOLVER              DECODING BOUNDARY
  (can have bugs)          (trusted)              (can have bugs)

Encoding bugs: Your S3 bucket has PublicAccessBlock.BlockPublicPolicy = true, but the translation emits has_public_read "bucket" "true". The assertion is wrong — the bucket is private, but Z3 thinks it’s public. Z3 faithfully returns sat based on the wrong input. The “vulnerability” doesn’t exist. You can’t tell from sat that the encoding was wrong.

Decoding bugs: Z3 returns sat with a model. You read the model and conclude “the bucket is directly accessible from the internet.” But the actual path is through the Cognito identity pool, not direct access. The model told you which variables were assigned, but the interpretation of those variables — what they mean in cloud terms — is your responsibility. Misread the model, misunderstand the vulnerability.

The solver is the most reliable component in the pipeline. The translation layers are where the bugs hide. And they’re the layers that get the least attention because everyone focuses on the solver.

What an orchestration layer provides

An orchestration layer wraps the solver with five capabilities that make the answer trustworthy, traceable, and actionable:

1. Encoding explanation — “Did the tool understand my configuration?”

Before the solver runs, you see what the tool extracted from your configuration, in your language:

Configuration Summary: 7 assets, 43 facts extracted

Asset: arn:aws:s3:::prod-phi (S3 Bucket)
  ├── Public read access: ENABLED
  │   Source: prod-phi-bucket.obs.json → access.public_read
  │   Fact: a3f8c2e91b04
  │
  ├── Encryption: AES256 (SSE-S3, not KMS)
  │   Source: prod-phi-bucket.obs.json → encryption.algorithm
  │   Fact: b7d1e4f03c89
  │
  └── Data classification: PHI
      Source: prod-phi-bucket.obs.json → tags.data_classification
      Fact: c9e2a1f04d77

Asset: arn:aws:cognito-identity:...:identitypool/abc (Identity Pool)
  ├── Unauthenticated access: ALLOWED
  │   Source: cognito-pool.obs.json → identity.access.allow_unauthenticated
  │   Fact: d4e5f6a7b8c9
  │
  └── Maps unauthenticated users to: arn:aws:iam::111122223333:role/AppUnauthRole
      Source: cognito-pool.obs.json → identity.cognito.unauth_role_arn
      Fact: e5f6a7b8c9d0

No SMT-LIB. No predicate names. “Public read access: ENABLED” — the security engineer compares this to their mental model of the bucket. If the bucket is private, the encoding is wrong and the engineer knows before the solver runs.

Every fact has a unique identifier and a traceable source showing which file and which property path produced it. This is the audit trail. When the solver’s answer doesn’t match expectations, the engineer traces the identifier back to the source and checks whether the encoding was correct.

2. Human verdict — “What does the result mean?”

After the solver runs, you see the answer in security language:

VERDICT: UNSAFE

An anonymous internet user can read PHI data from the prod-phi
bucket through the Cognito identity pool.

The forbidden state is reachable because:
  1. Identity pool allows unauthenticated access
     (cognito-pool.obs.json → identity.access.allow_unauthenticated = true)
  2. Unauthenticated users receive credentials for AppUnauthRole
     (cognito-pool.obs.json → identity.cognito.unauth_role_arn)
  3. AppUnauthRole has s3:GetObject permission
     (iam-role.obs.json → policies.attached_policies[0].Action)
  4. Target bucket contains PHI
     (prod-phi-bucket.obs.json → tags.data_classification = phi)

Not sat. Not a model with define-fun expressions. A four-step chain in plain English, each step linked to a specific file and property path. The security engineer reads it and knows exactly which settings create the vulnerability and where they live.

3. Fix guidance — “What do I do about it?”

FIX: Disable unauthenticated access on the identity pool.

  aws cognito-identity update-identity-pool 
    --identity-pool-id us-east-1:abc123 
    --no-allow-unauthenticated-identities

  Cost: $0. Time: 30 seconds.
  Effect: Breaks the chain at step 1.

VERIFICATION: After applying the fix, re-run the analysis.
  Expected result: SAFE
  (Z3 returns UNSAT — no assignment of principals, actions,
  and resources can satisfy the attack path conditions.)

The fix is a shell command. The cost is quantified. The effect names the specific chain step that breaks. The verification tells the engineer what to expect and explains the solver’s output in cloud terms.

4. Traceability — “Which property caused this?”

Every step in the verdict traces back to a specific property in a specific file through a unique identifier:

# The verdict says step 1 caused the chain.
# Trace identifier d4e5f6a7b8c9:

grep "d4e5f6a7b8c9" facts.jsonl

# Returns:
# fact_id: d4e5f6a7b8c9
# subject: pool-abc
# predicate: allows_unauthenticated = true
# source: cognito-pool.obs.json
# property: identity.access.allow_unauthenticated
# captured: 2026-05-01T00:00:00Z

One identifier. One grep. Full trace from the verdict to the configuration property, including when the snapshot was taken. No manual correlation across output files.

5. Encoding verification — “Can I trust the translation?”

The orchestration layer verifies its own encoding by comparing each extracted fact against the raw configuration:

Encoding verification: 43/43 facts verified ✓

Every extracted fact matches the corresponding property
in the observation file. The solver's input is consistent
with the configuration snapshot.

Or, when there’s a bug:

Encoding verification: 41/43 facts verified

MISMATCH:
  Fact d4e5f6a7b8c9:
    Extracted: allows_unauthenticated = "true"
    Observation: identity.access.allow_unauthenticated = "false"
    File: cognito-pool.obs.json
    → ENCODING BUG: fact says unauthenticated is allowed,
      but the configuration says it's disabled.
      The UNSAFE verdict may be incorrect.

No other tool in the market verifies its own translation layer. The security engineer doesn’t need to read SMT-LIB to trust the result — the tool proves its encoding is correct, or reports where it is wrong.

What you get without orchestration vs. with it

Capability Z3 alone With orchestration
Prove an attack path exists sat UNSAFE: anonymous user can read PHI data
Explain which settings cause it Read the SMT-LIB model Four-step chain with file names and property paths
Verify the encoding is correct Manually review assertions Automated: 43/43 facts match observations
Trace the finding to a file Grep through comments One identifier, one grep, full trace
Get the fix Derive it from the model Shell command, cost, time, expected result
Run multiple solvers Reformat per solver Same input, three solvers, consensus
Explain to an auditor Show them SMT-LIB Show them the chain in English with evidence

The right column is the product. The left column is a math library. Security engineers don’t need a math library — they need trustworthy answers in their language.

Why this matters more for compound detection

The orchestration layer is most valuable when the analysis spans multiple services. A single-service check (“is this bucket public?”) is simple enough to verify manually. A cross-service check (“can an anonymous user reach PHI data through a chain of Cognito → IAM → S3?”) involves three services, three configuration files, and dozens of properties.

The encoding has more places to be wrong. The verdict has more steps to explain. The traceability has more links to follow.

This is where raw solver output becomes dangerous. If Z3 returns sat on a three-service chain and the encoding has a bug in the IAM layer, the finding is a false positive. The security team triages it as critical, burns two sprints investigating, and discovers it was caused by a property path error in the translation code. The encoding verification catches that error before the solver runs.

If Z3 returns unsat and the encoding has a bug — a property that should be true was encoded as false — the finding is a false negative. The team thinks they’re safe. They’re not. The encoding verification catches that too.

The more complex the analysis, the more valuable the translation layer. Single-service checks can tolerate raw solver output. Cross-service compound detection cannot.

The bottom line

Don’t evaluate formal verification tools by which solver they use. Z3, cvc5, and Yices all produce correct answers to the questions they’re asked. The question is whether the question was asked correctly and whether the answer is translated back to your language.

Don’t accept sat as an answer. Accept “UNSAFE: an anonymous internet user can read PHI data from the prod-phi bucket through the Cognito identity pool, caused by these four settings in these three files, fixable with this one command for $0 in 30 seconds, verified by three independent solvers, encoding confirmed correct against 43 observation properties.”

That’s an answer. sat is a data point.

The orchestration layer described in this article is implemented in Stave, an open-source static analysis tool that evaluates cloud configurations via CEL predicates and exports standardized facts for consumption by nine independent reasoning engines. The encoding explanation, verdict translation, traceability, and encoding verification are built on Stave’s fact_id provenance chain — every fact carries a deterministic identifier linking the solver’s input to the specific observation file and property path that produced it. All analysis runs on air-gapped snapshots with no cloud credentials required.

I built a desktop IDE around SSH sessions because nothing else worked the way I do

I’m Wes, solo developer behind AidaIDE. I launched on Hacker News today and wanted to write up the longer version here for the dev.to crowd.

The problem

My day-to-day has me SSHing into about 15 different boxes: a couple of cloud VMs, a homelab, embedded boards on my LAN. For years my setup was PuTTY for one set, MobaXterm for another, VS Code for editing, and a separate password manager for keys.

Every tool was great at one thing and bad at the others. Switching between them lost context. Dropped tunnels lost scrollback. Pasting secrets into plaintext notes felt awful. And once — a Tuesday I will not forget — I deployed the wrong build to the wrong server because two terminal windows had matching tab labels.

That weekend I started building AidaIDE.

Three pieces I want to highlight for SSH-heavy work

1. Multi-session terminal UX

Tabs and split panes are table stakes. Beyond that:

  • Broadcast-to-selected-hosts when you need to run the same command on three boxes
  • Sessions that persist across IDE restarts
  • Keyboard-first command palette to jump to prod-db-2 in two keystrokes
  • Reattaching to a dropped connection doesn’t lose scrollback

2. Built-in encrypted vault

Your SSH keys, per-host passwords, and API tokens live in one encrypted store. Unlocked once at startup, then transparently used by the connection manager. Per-machine fingerprint binding means the vault can’t be yanked off a stolen laptop.

No more ~/.ssh/config drift across machines. No more pasting secrets into a notes app.

3. A connection tree that scales past 20 hosts

Group by project, tag, search, multi-select, run commands across a selection. The flat-bookmark-list UX in most SSH clients falls over the moment you have 20+ hosts; this one is built for the case where you have to think for a second about which jumpbox to start from.

What it isn’t

  • Not a web app. Desktop binary. Windows is the most polished today; macOS and Linux builds are catching up.
  • Not trying to be Vim or VS Code. It’s its own editor with sensible defaults, not a plugin platform.
  • $7/mo or $65/yr single-user license, cancel anytime. No free tier — but you can pull the plug after a month.

Feedback I’d love

  • Where the SSH UX still feels rough compared to Termius / MobaXterm / tmux+ssh / plain iTerm
  • The “I’d switch if it did X” deal-breakers
  • Performance on connection lists past 50 hosts if you have one

Link: aidaide.app

— Wes

Agent Harness: Running Multiple Parallel Agents for Deep Exploration

An agent harness: one central orchestrator node connected by neon data streams to 8 parallel worker agents on a dark background

Meta Description: Learn how agent harnesses orchestrate multiple parallel AI agents for deep exploration tasks — covering fan-out/fan-in architecture, aggregation strategies, real-world use cases like codebase analysis and security auditing, engineering challenges, and the evolving framework landscape.

Agent Harness: Running Multiple Parallel Agents for Deep Exploration

Table of Contents

  1. The Single-Agent Bottleneck
  2. What Is an Agent Harness?
  3. Why Parallelism? The Case for Multi-Agent Exploration
  4. Core Architecture: Fan-Out / Fan-In
  5. Deeper Patterns: Beyond Basic Fan-Out
  6. Result Aggregation Strategies
  7. Real-World Use Cases
  8. Engineering Challenges
  9. The Framework Landscape
  10. Future Directions
  11. Conclusion

The Single-Agent Bottleneck

Imagine asking a single developer to audit a 500,000-line codebase for security vulnerabilities — alone, in one sitting, reading every file sequentially from top to bottom. Even the most experienced engineer would miss things. Their attention degrades, their working memory fills up, and by the time they reach service number twelve, the context of service number one has long since faded.

A single AI agent has the same fundamental constraint: a finite context window. You can extend it — to 128K tokens, 200K tokens, even 1 million tokens — but you cannot escape the fact that a single reasoning thread exploring a large problem space will always be bounded by serial throughput. More critically, a single agent brings a single perspective. One reasoning chain. One set of activated associations. One path through the exploration graph.

This is the problem that agent harnesses with parallel exploration are designed to solve. Not by making individual agents smarter, but by running many of them simultaneously, each tackling a different slice of the problem space, and then intelligently synthesizing what they find.

This post is a deep technical dive into how agent harnesses work, why they matter, and how to engineer them well.

An agent harness: one central orchestrator node connected by neon data streams to 8 parallel worker agents on a dark background
An agent harness: one orchestrator, many parallel explorers, one synthesized result.

What Is an Agent Harness?

At its core, an agent harness is an orchestration layer that manages the lifecycle of multiple AI agents — spawning them, assigning tasks, monitoring execution, handling failures, and collecting results. The harness doesn’t do the intellectual work itself. It holds and directs the agents that do.

A harness operates across three conceptual tiers:

1. The Orchestrator — The top-level controller responsible for task decomposition and agent dispatch. It receives the high-level goal, decides how to split it into sub-tasks, and assigns each sub-task to a worker agent. The orchestrator may be an LLM itself (an “orchestrator agent”) or a deterministic system.

2. Worker Agents — Independent agents each operating within their own context window, executing their assigned sub-task without awareness of what other workers are doing. Each worker is a self-contained reasoning unit: it receives a scoped prompt, uses whatever tools are available to it, and returns a structured result.

3. The Aggregator — The layer responsible for combining outputs from all worker agents into a coherent final result. Aggregation can be as simple as concatenation or as sophisticated as having a dedicated meta-agent synthesize findings, resolve conflicts, and produce a narrative summary.

The key architectural insight is separation of concerns: the orchestrator knows what needs to be explored; the workers focus on how to explore their slice; the aggregator cares about what it all means together. This is fundamentally different from a pipeline (sequential steps) and from a single agent with tools (one context for all reasoning). An agent harness is a distributed system where the computational unit is an LLM inference call.

Why Parallelism? The Case for Multi-Agent Exploration

The motivation for running agents in parallel rests on three independent arguments: time, coverage, and cognitive diversity.

Time: O(N) to O(1)

If a single agent takes T seconds to process one sub-task, and you have N sub-tasks, sequential processing takes O(N*T) time. A parallel harness with N workers reduces this to O(T) — the time of a single sub-task, plus coordination overhead. For exploration tasks with dozens or hundreds of sub-tasks, this is the difference between minutes and hours.

Coverage: No Sub-Space Left Behind

A sequential agent must prioritize. It will naturally explore the most salient threads first and may never reach others if it runs out of context or hits a turn limit. A parallel harness assigns every sub-space to a dedicated agent, guaranteeing coverage. No forgotten modules, no skipped documents, no deprioritized attack surfaces.

Cognitive Diversity: Multiple Perspectives

When you give the same high-level goal to multiple agents with different system prompts, tool sets, or contextual framings, they find different things. One agent analyzing a codebase from a “data flow” lens surfaces different issues than one focused on “error handling.” Context isolation amplifies this: each agent reasons with sharper focus on its specific slice, free from the noise of the broader problem.

Core Architecture: Fan-Out / Fan-In

The canonical execution model for agent harnesses is Fan-Out / Fan-In — a pattern borrowed from parallel computing and MapReduce, adapted for LLM-powered workers.

The pattern has four phases:

Architecture diagram showing Fan-Out / Fan-In pattern: orchestrator at top, 5 parallel agents in middle, aggregator at bottom with neon arrows
The Fan-Out / Fan-In pattern: decompose → dispatch in parallel → aggregate.

Phase 1: Decomposition

The orchestrator analyzes the input goal and produces a list of independent sub-tasks. Independence is critical — sub-tasks that depend on each other cannot be safely parallelized. Good decomposition strategies include domain decomposition (split by module/service/file), perspective decomposition (same input, different analytical lenses), sampling decomposition (random subsets from a large corpus), and hierarchical decomposition (split into themes, then sub-split each).

Phase 2: Fan-Out (Dispatch)

The orchestrator spawns N worker agents simultaneously, each initialized with its specific sub-task prompt, access to relevant tools, and any globally applicable shared context. Workers execute entirely in parallel with no inter-agent communication.

Phase 3: Parallel Execution

Each worker agent operates autonomously — calling tools, reasoning through its sub-task, handling errors — until it produces a result. The harness monitors all workers concurrently, handling timeouts, retries on transient failures, and early termination on unrecoverable errors.

Phase 4: Fan-In (Aggregation)

As workers complete, results are collected. Once all (or a sufficient quorum of) workers finish, the aggregation step runs. This may be a deterministic merge, a secondary LLM call to synthesize findings, or a structured pipeline routing different result types to different handlers.

The elegance of this architecture: it composes naturally. The orchestrator itself can be a worker in a higher-level harness, making the entire pattern recursively nestable.

Deeper Patterns: Beyond Basic Fan-Out

Hierarchical / Tree-Structured Harnesses

For deeply nested problem spaces, a single layer of parallel agents may be insufficient. A hierarchical harness introduces multiple levels: a top-level orchestrator fans out to mid-level coordinators, each of which fans out to their own pool of leaf workers. Results bubble up through successive aggregation steps.

This pattern mirrors the natural hierarchy of large codebases: top-level assigns one coordinator per repository, each coordinator assigns one worker per service, each worker drills into individual files.

Recursive Agent Spawning

Some harnesses allow worker agents to spawn sub-agents when their assigned task exceeds a single context window. The agent makes an explicit tool call to the harness runtime, which creates a new worker and returns its result asynchronously. This produces dynamically growing trees of agents — a natural fit for open-ended exploration where sub-task complexity is unknown upfront.

The risk: without depth limits and cost budgets, recursive spawning creates exponential agent proliferation. Every production harness supporting this pattern must enforce maximum tree depth and per-subtask token budgets.

Competitive / Ensemble Harnesses

Rather than decomposing a task, some harnesses run multiple agents on the same task with different strategies, system prompts, or model choices. Results are compared, and the best answer is selected via voting, confidence scoring, or a judge agent. This ensemble approach trades compute for accuracy and is common in high-stakes scenarios where correctness outweighs cost.

Swarm Topologies

Inspired by swarm intelligence, experimental harnesses allow agents to communicate with neighbors via a defined adjacency graph. An agent discovering a promising finding can broadcast a signal that biases the exploration direction of adjacent agents — emergent coordination without centralized control. Powerful in theory; challenging to debug and predict in production.

Result Aggregation Strategies

How you aggregate parallel results is as important as how you run the agents. The strategy determines quality, verbosity, and reliability of the final output.

Strategy Description Best For Tradeoff
Union Merge Concatenate all results Comprehensive reports High verbosity, duplication
Voting / Quorum Keep only findings N/M agents agree on High-confidence extraction May discard rare valid findings
Hierarchical Synthesis Meta-agent reads all outputs, writes unified summary Narrative reports Extra LLM cost, latency
Confidence-Weighted Rank by model confidence, keep top-K Ranked recommendations Depends on reliable self-assessment
Semantic Deduplication Embed findings, cluster by similarity, keep one per cluster Removing redundant discoveries Embedding cost, cluster quality
Critic-Review Dedicated critic agent challenges each finding High-stakes validation Significant extra compute

Colorful data streams from multiple agents converging into a single bright central node, representing result aggregation
Aggregation: the moment parallel exploration becomes unified intelligence.

In practice, most production harnesses chain multiple strategies: semantic deduplication first, then hierarchical synthesis, then a critic pass on high-priority findings.

Real-World Use Cases

Large Codebase Exploration

Assign one agent per service or module in a large microservices architecture.

Multiple glowing AI robot agents exploring a 3D file system tree representing a large codebase
Parallel agents assigned to different modules explore the entire codebase simultaneously.
Each agent reads its assigned code, identifies patterns, flags issues, and documents behaviors. The harness aggregates into a cross-cutting architectural summary no single agent could produce in reasonable time. Code modules are naturally independent — the fan-out boundary maps directly onto the module boundary.

Security Vulnerability Scanning

Assign agents to explore different attack surfaces simultaneously: authentication flows, SQL query construction, dependency CVEs, API endpoint input validation. Each agent is seeded with a specific threat model. Running multiple agents on the same endpoint with different attack personas produces more comprehensive coverage than a single agent switching modes.

Research Synthesis

Given a corpus of 50 papers, assign one agent per paper. Each agent extracts key claims, methodologies, results, and limitations. The aggregator identifies agreements, contradictions, and open questions across the corpus — producing a systematic review in minutes rather than weeks.

Multi-Perspective Analysis

For complex decisions (architecture reviews, incident post-mortems), run the same document through multiple agents simultaneously, each with a different analytical persona: security engineer, performance engineer, product manager, reliability engineer. The aggregated output captures concerns any single perspective would miss.

Engineering Challenges

API Rate Limits and Throughput Budgets

Spawning 50 agents simultaneously will immediately saturate most tier-1 API quotas (RPM/TPM). Mitigations: exponential backoff with jitter, agent queuing with configurable concurrency limits, multi-provider routing across OpenAI/Anthropic/Azure OpenAI, and pre-warming agent pools during low-traffic windows.

Token Budget Management

Each parallel agent consumes tokens independently. A harness running 20 agents, each with 4K-token context generating 2K-token outputs, burns 120K tokens per cycle. Best practices: set hard per-agent output token limits, use smaller models for leaf workers and larger models for orchestrators, implement per-run cost tracking.

Context Isolation and Shared State

Agents may need access to shared facts (e.g., a list of already-discovered issues to avoid re-reporting). Design options: a read-only shared context injected at spawn time, or a write-enabled external store (Redis, vector DB) that agents query without contaminating each other’s reasoning. The latter requires locking strategies and eventual consistency handling.

Semantic Deduplication

When N agents explore overlapping domains, they independently rediscover the same findings. Exact-match deduplication is insufficient — “missing input validation on user_id parameter” and “no sanitization of user_id field” are the same finding expressed differently. Semantic deduplication via embedding similarity (cosine distance above threshold) is standard but requires careful threshold tuning.

Hallucination Amplification

The most insidious risk: if a flawed assumption is embedded in the shared task description, every agent inherits and potentially amplifies that error. Unlike a single-agent mistake affecting one context, a harness-level error multiplies across all workers simultaneously. Mitigations: ground agents with RAG-retrieved facts, include a critic agent in the aggregation pipeline, and design task decompositions that are factually anchored.

Failure Modes and Partial Results

In any run of N parallel agents, some will fail. The harness must decide: wait for all agents (maximizes coverage), proceed with a quorum (balanced), or fail fast (conservative). Most production harnesses use tiered classification: critical agents trigger full reruns on failure; optional agents are subject to quorum logic.

The Framework Landscape

LangGraph treats agent execution as a stateful graph with nodes and edges. Parallel execution uses the Send API for dynamic fan-out. Strength: fine-grained control. Challenge: steep complexity curve for non-trivial graphs.

AutoGen (Microsoft) models agents as conversational entities. Parallel execution through async message passing. Best for multi-turn reasoning patterns and structured agent dialogue.

CrewAI uses the “crews and tasks” metaphor with explicit role assignments. Async crew execution supports parallel task running with built-in delegation. Fast to prototype; less flexible at the edges.

OpenAI Swarm (experimental) emphasizes minimal abstraction — agents as simple functions, harness manages handoffs. Lightweight by design, for teams wanting full execution control without framework overhead.

Custom harnesses implement fan-out/fan-in directly over asyncio.gather, a job queue (Celery, Redis Queue), or a workflow orchestrator (Temporal, Prefect). The appeal: full observability, no framework lock-in, precise control over retry logic, rate limiting, and cost accounting.

Future Directions

Adaptive Parallelism

Static parallelism is a blunt instrument. The next frontier: harnesses that dynamically adjust agent count based on measured uncertainty. When early scout agents reveal high complexity in a sub-space, the harness spins up more workers there. When results converge and redundancy rises, it scales down. This cost-aware, adaptive model optimizes coverage vs. cost in real time.

Self-Organizing Agent Networks

Research into decentralized coordination explores harnesses where agents themselves decide when to spawn sub-agents, which findings to broadcast to neighbors, and when to terminate — drawing from ant colony optimization and stigmergy. Emergent exploration without centralized orchestration. The practical challenge: predictability and debuggability that engineering teams demand in production.

Agent Specialization and Role Evolution

Today most harnesses run homogeneous workers. Future harnesses will maintain pools of specialized agents: fast cheap models for breadth-first scanning, slow expensive models for depth analysis, tool-specific agents optimized for code execution or retrieval, adversarial critics tuned for challenge. The orchestrator’s job will resemble a talent agency — matching the right agent profile to the right sub-task.

Evaluation Harnesses

A powerful emerging use case: running parallel agents not to produce results, but to evaluate them. Sending a candidate model’s output to 20 independent judge agents simultaneously, each scoring on a different dimension, produces faster and more robust evaluation than sequential human review. The harness becomes infrastructure for scalable, automated quality assurance.

Conclusion

The agent harness is one of the most powerful architectural patterns in the modern AI engineering toolkit. By decomposing complex exploration tasks across many parallel agents, harnesses transcend the fundamental limitations of a single context window — delivering orders-of-magnitude improvements in speed and coverage, and unlocking cognitive diversity no single agent can replicate.

The fan-out/fan-in model provides a clean, composable foundation. Sophisticated aggregation strategies transform raw parallel output into coherent, actionable intelligence. And the engineering challenges — API rate limits, token budgets, context isolation, hallucination risks — all have established mitigation patterns.

As agent frameworks mature and LLM inference costs fall, agent harness parallel exploration will become standard infrastructure for anyone building systems that need to reason over large, complex problem spaces. The question won’t be whether to run parallel agents — it will be how many, in what topology, with what aggregation strategy, and at what cost.

Start simple: a flat fan-out with union aggregation via asyncio.gather. Measure coverage and quality. Then layer in deduplication, synthesis, and adaptive parallelism as your use case demands. The single-agent era was a starting point. The parallel exploration harness is where the real capability unlocks.

Found this useful? Follow for more deep dives into AI agent architecture, distributed systems, and the engineering patterns powering the next generation of intelligent systems.

How I Built and Shipped 4 CLI Tools in One Weekend

Last Friday night I decided to stop being a consumer and start being a builder. By Sunday midnight, I had 4 CLI tools live on npm and GitHub. Here’s exactly how I did it.

The Rules I Set

  1. Zero dependencies — no node_modules black hole
  2. Single file per tool — no build step, no config
  3. npx-ready — no install required
  4. Actually useful — no todo apps

Tool #1: ScaffoldX

Problem: Every new project = 30 minutes of boilerplate.

Solution: npx scaffoldx-cli → pick template → start coding in 3 seconds.

12 templates. React, Next.js, Express, FastAPI, Chrome Extensions, Discord Bots. Every single one compiles with zero errors out of the box.

npx scaffoldx-cli

⭐ GitHub | npm

Tool #2: dotguard

Problem: Accidentally committed .env with real keys. We’ve all done it.

Solution: npx @wuchunjie/dotguard scans every .env for API keys, private keys, passwords, and database URLs.

npx @wuchunjie/dotguard .

⭐ GitHub

Tool #3: gitpulse

Problem: “What did I even do this week?”

Solution: Terminal dashboard with contributor bars, file type breakdown, daily heatmap.

npx @wuchunjie/gitpulse

⭐ GitHub

Tool #4: snippetx

Problem: I’ve written the same Express boilerplate 100 times and I keep Googling it.

Solution: Save it once, search it forever.

echo 'app.get("/api", (req, res) => res.json({ok: true}))' | npx @wuchunjie/snippetx add route js
npx @wuchunjie/snippetx search express

⭐ GitHub

The Stack

  • Language: Node.js (no TypeScript build step needed for small tools)
  • Editor: VS Code
  • Publishing: npm + git push
  • Promotion: Dev.to, GitHub, word of mouth

What I Learned

  1. Ship ugly. My first versions were 50 lines. I improved them after publishing.
  2. npm is instant. npm publish takes 2 seconds. Stop overthinking.
  3. Zero-deps ages well. No security alerts, no breaking changes, no maintenance burden.
  4. GitHub stars come slowly. But every star = one real person who found it useful.

What’s Next

More templates for ScaffoldX. More scanning rules for dotguard. More analytics for gitpulse.

And maybe a 5th tool. Got an idea? Drop a comment.

Building in public. Buy me a coffee if these tools saved you time ☕

Migrating a ScyllaDB Cluster the “Brain Transplant” Way

Ever tried migrating a ScyllaDB/Cassandra cluster when traditional replication tools are off the table?
I went a little “mad scientist” and pulled off what I call a cluster brain transplant.

The idea: copy the raw data files while the source cluster keeps running, then cut over with minimal downtime.
Risky? Yes. Crazy? Definitely. But it worked — three times in a row.
Here’s the story of how I did it, why I had no other choice, and what I learned along the way.

Sometimes, traditional migration methods just don’t work.
That was the situation I found myself in when moving a ScyllaDB cluster that was running inside managed Kubernetes.
Two-way connectivity between old and new clusters wasn’t possible, which meant I had to get creative.

After a few experiments (and a bit of courage), I pulled off what I now call the “brain transplant” migration.
It’s not the official way, but it worked — and worked surprisingly well.

I have migrated a production database with as little as possible downtime and 100% data consistency.

The Challenge

Normally, ScyllaDB migrations rely on tools like sstableloader or replication between clusters.
But when your cluster runs in managed Kubernetes, networking and connectivity restrictions can get in the way.
In my case, it wasn’t possible to directly link the old and new clusters in both directions.

That left me with a crazy idea: what if I just copied the entire brain of the cluster —
all the data files, commitlogs, and system state — into a brand-new cluster, and then carefully brought it to life?

The Migration Steps

Here’s how it went down:

Prepare the destination cluster

This is one to one copy solution, so source and destination clusters must have the same amount of nodes and identical configuration .

  • Install destination cluster. Use the Scylla official documentation to install a brand-new cluster.
  • Configuration. The most crucial is the cluster name parameter. Make sure that source and destination clusters have same name in config file.
  • Stop The destination. Shutdown all nodes in destination cluster and delete ALL data directories.

1. Full rsync copy

I started with a one-to-one rsync of all Scylla data from the source cluster to the destination cluster.
This took a long time (not surprising, given the dataset size), but it was straightforward.
Importantly, the source cluster stayed online and continued serving applications during this.
Here the exact rsync command that I have used. Assuming we need to migrate 3 nodes.

On source node1: rsync -val --exclude 'commitlog/*' /var/lib/scylla/* destinationsrv1:/var/lib/sylla
On source node2: rsync -val --exclude 'commitlog/*' /var/lib/scylla/* destinationsrv2:/var/lib/sylla
On source node3: rsync -val --exclude 'commitlog/*' /var/lib/scylla/* destinationsrv3:/var/lib/sylla

2. Incremental rsync runs

After the initial heavy lift, I ran multiple incremental rsyncs. Each one was much faster than the last,
because only changed data needed to be copied. Again, the source cluster kept working during this step,
so downtime was still zero.

3. The cutover

When it was time to switch, I stopped the applications pointing to the old cluster.
The source cluster was still technically alive, but no longer serving traffic.
This was the official “downtime” moment.

4. Booting up the new cluster

On the destination side, I started the seed node first, waited for it to come up,
then started the remaining nodes one by one. This part took some patience.
The logs were noisy with strange-looking system messages, but eventually all nodes settled down and came online.

5. Validating the cluster

With all nodes running, nodetool status confirmed that the new cluster was healthy.
I could connect with cqlsh, query some tables, and see real data.

6. Repairing

To make sure everything was consistent, I ran nodetool repair on each destination node, one by one.
This is a normal part of cluster maintenance, and it completed without errors.

7. Final shutdown of the source

Once I was confident the destination cluster was working correctly, I shut down the old Kubernetes-based cluster for good.

8. Phantom memories (The final step)

As we did a brain transplant, the new system will have “Phantom memories” about previous nodes in a cluster.
nodetool status will show clean cluster with new nodes only, you will be able to get and set data, but not able to do metadata changes, add or remove nodes.
The reason is that there is still information about nodes from source cluster, which does not exist anymore, but Scylla tries to connect and get some information.
The symptom messages in log like these :

scylla:  [shard  0:main] raft_group_registry - (rate limiting dropped 2999 similar messages) Raft server id d9756728-be49-4cbf-8e2c-417aa8b917c1 cannot be translated to an IP address.
scylla:  [shard  0:main] raft_group_registry - (rate limiting dropped 2999 similar messages) Raft server id e671084b-c41f-4eec-a73c-4c2eaf48ac38 cannot be translated to an IP address.
scylla:  [shard  0:main] raft_group_registry - (rate limiting dropped 2999 similar messages) Raft server id 96edc6f8-4e36-4044-ab53-c0a95a3873f7 cannot be translated to an IP address.

Solution

Monitor logs for messages like above and run nodetool removenode ID on any of cluster member nodes.

nodetool removenode d9756728-be49-4cbf-8e2c-417aa8b917c1
nodetool removenode e671084b-c41f-4eec-a73c-4c2eaf48ac38
nodetool removenode 96edc6f8-4e36-4044-ab53-c0a95a3873f7

On success, nothing should be printed on stdout.

Continue monitoring logs and remove all phantom nodes.

Manual recovery and Raft reset.

This procedure is needed as the nodes have changed their IDs, but the Raft database is not cleared.
Even after performing Phantom nodes removal, described above, yor cluster may, most probably will, keep old nodes in Raft.
So manual removal is required.

During this period tour cluster will be rolling restarted and put in a RECOVERY mode.

Procedure

  1. Perform the following query on every alive node in the cluster, using e.g. cqlsh:
cqlsh> UPDATE system.scylla_local SET value = 'recovery' WHERE key = 'group0_upgrade_state';
  1. Perform a rolling restart of your alive nodes.
  2. Verify that all the nodes have entered RECOVERY mode when restarting; look for one of the following messages in their logs:
group0_client - RECOVERY mode.
raft_group0 - setup_group0: Raft RECOVERY mode, skipping group 0 setup.
raft_group0_upgrade - RECOVERY mode. Not attempting upgrade.
  1. Remove all your dead nodes using the node removal procedure.
  2. Remove existing Raft cluster data by performing the following queries on every alive node in the cluster, using e.g. cqlsh:
cqlsh> TRUNCATE TABLE system.topology;
cqlsh> TRUNCATE TABLE system.discovery;
cqlsh> TRUNCATE TABLE system.group0_history;
cqlsh> DELETE value FROM system.scylla_local WHERE key = 'raft_group0_id';
  1. Make sure that schema is synchronized in the cluster by executing nodetool describecluster on each node and verifying that the schema version is the same on all nodes.
  2. We can now leave RECOVERY mode. On every alive node, perform the following query:
cqlsh> DELETE FROM system.scylla_local WHERE key = 'group0_upgrade_state';
  1. Perform a rolling restart of your alive nodes.
  2. The Raft upgrade procedure will start anew. Verify that it finishes successfully.

Why This Worked

At first glance, this approach sounds risky — copying live data files and commitlogs while the source cluster is still running. And yet,
Scylla’s design and eventual consistency model made it surprisingly resilient.

By repeatedly syncing the data and commitlogs, then repairing the new cluster after startup,
I ended up with a clean and working copy. It’s a bit like pausing a brain,
moving it into a new body, and jump-starting it again.

Lessons Learned

  • It’s not the official method. This was a pragmatic hack, not a documented procedure. If you can use sstableloader or proper replication, do that instead.
  • Incremental rsync is a lifesaver. Each run got faster and gave me confidence that the final cutover would be smooth.
  • Expect noisy logs. Don’t panic if the new nodes shout a lot when starting. Let them stabilize.
  • Repair is mandatory. Running nodetool repair at the end ensures consistency across the new cluster.

Final Thoughts

Would I recommend this approach for everyone? Probably not.
But in constrained environments, sometimes you need to think outside the box.
For me, the “brain transplant” worked — three times in fact, with consistent results.

It’s one of those migration war stories worth sharing.
If you’re ever stuck without traditional migration paths,maybe this story gives you a bit of inspiration (and courage) to try something unconventional.

✅ TL;DR: I migrated a ScyllaDB cluster by rsync’ing its data and commitlogs into a new cluster,
booting it up, repairing it, and cutting over apps — a pragmatic “brain transplant” that worked when standard tools weren’t an option.

Beyond the Hype: Building Production-Grade MCP Servers for AI Integration

The Model Context Protocol (MCP) is reshaping how AI applications connect to the world. Introduced by Anthropic in November 2024, MCP provides a standardized, open-source framework for Large Language Models (LLMs) to interact with external tools, data sources, and workflows. Instead of every AI platform building custom integrations for every backend system, MCP proposes a universal adapter pattern—an MCP server sits between the AI client (like Claude, ChatGPT, or GitHub Copilot) and the data or service.

But as with any emerging standard, the gap between a working prototype and a production-ready server is vast. In this article, we’ll dissect the MCP server architecture, walk through a concrete implementation, explore real-world pitfalls, and outline patterns for secure, scalable deployments.

Understanding the MCP Server Architecture

At its core, MCP follows a clean client-server model. The MCP Host (the AI application) connects to one or more MCP Servers, each of which exposes a well-defined set of capabilities. Communication happens over a transport layer that abstracts the underlying connection mechanism—either stdio for local processes or Streamable HTTP for remote servers.

flowchart LR
    A[AI Client<br/>e.g., Claude Desktop] -->|MCP Protocol| B[MCP Host]
    B --> C{MCP Transport Layer}
    C -->|stdio| D[MCP Server A<br/>Local File System]
    C -->|Streamable HTTP| E[MCP Server B<br/>Remote Database]
    C -->|Streamable HTTP| F[MCP Server C<br/>External API]
    D --> G[Resources & Tools]
    E --> H[Resources & Tools]
    F --> I[Resources & Tools]

    style A fill:#4a90d9,color:#fff
    style B fill:#f5a623,color:#fff
    style C fill:#7ed321,color:#fff
    style D fill:#d0021b,color:#fff
    style E fill:#d0021b,color:#fff
    style F fill:#d0021b,color:#fff

Diagram: MCP Architecture showing transport abstraction and multiple server connections.

This transport abstraction is a key design decision. The same server implementation can run locally via stdio for development or be deployed as a remote HTTP service for production. The modelcontextprotocol.io specification defines this clearly, allowing developers to choose the right transport for their security and scalability needs.

The Resource-Tool-Prompt Triad

Every MCP server exposes three core primitives, as documented in the official SDK documentation:

  1. Resources: Data that can be read—files, database records, API responses. These are the “what” the AI can access.
  2. Tools: Functions the AI can invoke—search, calculate, send email. These are the “how” the AI can act.
  3. Prompts: Pre-written templates for common interactions. These guide the AI’s behavior.

This triad provides a structured, discoverable interface. When an AI client connects to an MCP server, it can introspect the available resources, tools, and prompts, enabling dynamic adaptation without hardcoded integrations.

Building a Production-Ready MCP Server

Let’s move from theory to practice. Below is a minimal but complete MCP server implementation in TypeScript, based on the official SDK. This server provides a simple weather lookup tool.

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// 1. Create server with capability declaration
const server = new Server(
  {
    name: "example-weather-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {}, // Declares that this server provides tools
    },
  }
);

// 2. Define the tool interface
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "get_weather",
      description: "Get current weather for a city",
      inputSchema: {
        type: "object",
        properties: {
          city: { type: "string" },
          units: { type: "string", enum: ["metric", "imperial"] },
        },
        required: ["city"],
      },
    },
  ],
}));

// 3. Implement tool logic with error handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "get_weather") {
    const city = String(request.params.arguments?.city);
    const units = String(request.params.arguments?.units || "metric");

    // In production, call a real weather API here
    // Add retry logic, rate limiting, and monitoring
    try {
      const temperature = units === "metric" ? 22 : 72;
      const condition = "Sunny";

      return {
        content: [
          { 
            type: "text", 
            text: `Weather in ${city}: ${condition}, ${temperature}°${units === 'metric' ? 'C' : 'F'}` 
          }
        ],
      };
    } catch (error) {
      // Return structured error information
      return {
        isError: true,
        content: [{ type: "text", text: `Failed to fetch weather: ${error.message}` }],
      };
    }
  }
  throw new Error("Tool not found");
});

// 4. Connect via stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);

console.error("Weather MCP server running on stdio");

Code example: A minimal MCP server with proper error handling and structured responses.

This example demonstrates several production considerations:

  • Capability Declaration: The server explicitly declares it provides tools. This allows the AI client to understand what’s available.
  • Input Validation: The inputSchema defines expected parameters and their types.
  • Structured Error Handling: Instead of crashing, the server returns an isError response with a descriptive message.
  • Logging to stderr: The server logs to stderr, keeping stdout clean for the MCP protocol messages.

Production Pitfalls and Hard Lessons

The MCP ecosystem is maturing rapidly, but early adopters have already encountered significant challenges. Understanding these pitfalls is crucial for any team deploying MCP servers in production.

Data Leakage from Multi-Tenant Servers

In early 2026, Asana’s MCP feature suffered a critical bug that exposed customer data from one organization to other MCP users. As reported by BleepingComputer, a software bug in the tenant isolation logic allowed cross-organization data access. This incident underscores a fundamental requirement: every MCP server operating in a multi-tenant environment must implement strict tenant isolation at the database and application layers.

Chained Vulnerabilities in Official Servers

Even Anthropic’s own Git MCP server was not immune. Security researchers discovered chained flaws that enabled arbitrary file access and remote code execution, as detailed by SiliconAngle. The vulnerabilities were particularly dangerous because they could be triggered through normal tool invocations, turning a useful integration into an attack vector.

Lesson: Treat MCP servers as high-risk endpoints. They have direct access to backend systems and are invoked by AI models that may be prompted to exploit them. Regular security audits, input sanitization, and least-privilege principles are non-negotiable.

The Integration Purgatory Problem

Workato’s research, announced via BusinessWire, revealed that many AI initiatives stall because MCP servers are not production-ready. Common issues include:

  • Missing error handling and retry logic
  • No rate limiting or circuit breakers
  • Lack of observability (logging, metrics, tracing)
  • Inadequate authentication and authorization

Workato launched production-ready MCP servers specifically to address this “integration gap” that keeps AI initiatives in pilot purgatory.

Enterprise Patterns for Secure MCP Deployments

Capability-Based Security

Production MCP servers should implement capability-based security, where each server declares exactly what resources and tools it exposes. The AI client then enforces that the server only accesses permitted data. This pattern, recommended by Security Boulevard, prevents excessive permissions and limits blast radius in case of compromise.

The Enterprise Registry Pattern

Microsoft’s MCP Center, built on Azure API Center, provides a centralized registry for MCP servers. This enables:

  • Governance: Centralized policy enforcement and approval workflows
  • Discoverability: AI clients can find available servers dynamically
  • Lifecycle Management: Versioning, deprecation, and retirement of servers

For organizations deploying multiple MCP servers, a registry pattern is essential for managing complexity at scale.

Transport Security Considerations

The choice between stdio and Streamable HTTP transport has security implications:

Transport Use Case Security Considerations
Stdio Local development, single-user Simple, no network exposure; limited scalability
Streamable HTTP Production, multi-user Requires TLS, authentication, rate limiting

For remote servers, always enforce TLS, implement OAuth2 or API key authentication, and use network segmentation to limit exposure.

Key Takeaways

  • MCP standardizes AI-tool integration through a clean client-server architecture with transport abstraction, backed by major players including Anthropic, OpenAI, and Microsoft.
  • Production MCP servers must prioritize security—implement tenant isolation, capability-based permissions, and regular security audits to prevent data leakage and code execution vulnerabilities.
  • Observability and resilience are non-negotiable—include error handling, rate limiting, retry logic, and monitoring from day one to avoid the “integration purgatory” that stalls AI initiatives.
  • Choose your transport wisely—stdio for simplicity and local use, Streamable HTTP for remote deployments with proper authentication and TLS.
  • Enterprise registries like Microsoft’s MCP Center enable governance, discoverability, and lifecycle management for MCP server deployments at scale.

After 5 years of Go services, here’s the boilerplate I wish existed

TL;DR: I open-sourced rock288/go-mongo-boilerplate — a Go 1.25 service template that ships the boring production stuff (observability, retry, DLQ, SSRF-safe HTTP, health splits, graceful shutdown) so you don’t write it for the 7th time. Click “Use this template” and start with make scaffold name=Order.

The problem with every Go boilerplate I’ve used

I’ve started six Go services in the last five years. The first 200 lines were always the same:

  • Wire up a slog handler that auto-injects trace_id
  • Pick a config library (koanf? viper? envconfig?) and write a Validate()
  • Add Mongo + an event.CommandMonitor that doesn’t leak memory
  • Build a Kafka consumer with retry topic + DLQ
  • Hand-roll graceful shutdown for two consumer goroutines
  • Realize the HTTP client is wide-open to SSRF and patch the dialer
  • Split /healthz (liveness) from /readyz (readiness)
  • Spend an afternoon making golangci-lint, mockery, and Wire all happy together

Most Go boilerplates on GitHub solve one of these. None of the ones I found solved all of them in a way that felt opinionated rather than kitchen-sink-y.

So I wrote one. Then I wrote it again. Then I extracted the third iteration into a public template.

This post is a tour of the parts that took me the most rewrites to get right — and the trade-offs I’d argue with you about.

1. Package-by-feature + a tiny platform/ layer

The most common boilerplate mistake: models/, controllers/, services/ directories. That layout scales to maybe four features before you grep for User and get 14 files in 7 packages.

internal/
  user/                 ← one feature, all in one place
    model.go
    repository.go
    service.go
    handler.go
    dto.go
    routes.go
    errors.go
    wire.go
  role/                 ← another feature
  platform/             ← cross-feature infrastructure
    config/  logger/  database/  httpserver/  httpclient/
    kafka/   sqs/      health/   observability/  resilience/
    pagination/         httperror/

The hard rule: features call each other only through exported interfaces (UserService, not userService). Cross-feature dependencies on concrete types are a code smell. Lint rule on this would be nice — open to suggestions.

The internal/platform/ layer is everything that doesn’t belong to a single feature. Any feature may import platform; features may not import each other’s internal symbols.

Adding a new feature is one command:

make scaffold name=Order

A bash script clones internal/user/, runs perl to rename identifiers, and prints the wire-up checklist. From make scaffold to a working GET /orders endpoint is ~10 minutes.

2. MongoDB v2 driver — and why you probably haven’t migrated yet

The go.mongodb.org/mongo-driver/v2 GA’d, but most templates on GitHub are still on v1. The v2 driver has a cleaner cursor API, generics support, and — crucially for production — there’s still no official OTel instrumentation package.

So I wrote a custom event.CommandMonitor:

// internal/platform/database/mongo_otel.go
client, err := mongo.Connect(options.Client().
    ApplyURI(uri).
    SetMonitor(NewCommandMonitor(tracer)))

The monitor keeps a RequestID → span map populated on CommandStarted and consumed on CommandSucceeded / CommandFailed. The catch: if a command never completes (network drop, server restart) the entry leaks. So there’s a janitor goroutine that purges entries older than 2 minutes.

// Sketch — full impl in the repo
type CommandMonitor struct {
    inflight sync.Map  // RequestID → spanEntry
}

func (m *CommandMonitor) Started(ctx, evt) {
    _, span := tracer.Start(ctx, "mongo."+evt.CommandName)
    m.inflight.Store(evt.RequestID, spanEntry{span, time.Now()})
}

func (m *CommandMonitor) Succeeded(ctx, evt) {
    if v, ok := m.inflight.LoadAndDelete(evt.RequestID); ok {
        v.span.End()
    }
}

Hot take: every project I’ve seen that uses MongoDB in Go has at least one production memory leak in their tracing layer. Mine probably did too until I wrote that janitor.

3. Kafka retry + DLQ that actually works under load

This is the part of the template I’ve rewritten the most times across jobs.

Rules I now live by:

  1. Three topics per logical event: events, events.retry, events.dlq. No magic, no in-memory queues.
  2. Retry topic has its own consumer group (<group>-retry). When it polls a message, it sleeps min(base * 2^n, max) before republishing to the main topic. This separates retry latency from main-topic throughput.
  3. x-retry-count and x-error-reason are headers, not body fields. The body is opaque to the platform.
  4. Inbound x-retry-count and x-error-reason are stripped on republish. Attacker can’t pre-set retry count to force DLQ. (Spotted on a real pentest. Yes, it was me.)
  5. Idempotency key auto-generated on publish if not provided. Consumers dedupe via this key, not message offset.
// The contract the platform exposes
type MessageHandler interface {
    Handle(ctx context.Context, record *kgo.Record) error
}

// Return:
//   nil                                  → commit offset (ACK)
//   errors.Is(err, context.Canceled)     → no commit, no republish (shutdown)
//   errors.Is(err, kafka.ErrNonRetryable) → straight to DLQ
//   any other error                      → retry topic with incremented count

W3C traceparent is injected on the producer side and extracted on the consumer side so distributed traces don’t break when crossing the broker.

producer.Publish(ctx, "user-events", key, body)
//                  ↑
//                  span context auto-propagated into the record header

The failure path calls ForceSample() so retry/DLQ traffic always shows up on the dashboard — sampling shouldn’t hide errors.

4. SQS as a second broker — same API, same routing

Plenty of teams pick SQS over Kafka when they’re AWS-only and don’t need replay history. So the template ships both, side-by-side, with deliberately identical routing semantics:

// Kafka
type kafka.MessageHandler interface { Handle(ctx, *kgo.Record) error }

// SQS — same shape
type sqs.MessageHandler interface { Handle(ctx, *types.Message) error }

// Same return contract:
//   nil → ACK
//   ctx.Canceled → drop
//   ErrNonRetryable → DLQ
//   other err → retry queue with backoff

The same make scaffold walkthrough works for both. The cmd/worker binary runs four goroutines via errgroup.WithContext (Kafka main+retry, SQS main+retry); first non-nil cancels the shared ctx and all consumers shut down together.

Gotchas the docs warn you about:

  • SQS MessageDelaySeconds caps at 900s. Backoff > 15 min silently clamps. (For longer backoffs you need an external scheduler.)
  • MessageAttributes limit is 10 keys; 3 are reserved (traceparent, tracestate, x-idempotency-key). Producer rejects with ErrTooManyAttributes if the caller exceeds the budget.
  • Standard SQS does NOT guarantee ordering — don’t expect Kafka-partition semantics.

LocalStack for dev:

make localstack-up
make sqs-create-queues   # provisions events / events-retry / events-dlq + RedrivePolicy

Production: queues are provisioned by your IaC (Terraform/CDK). The worker only needs sqs:Send/Receive/Delete/GetQueueUrl/ChangeMessageVisibility — no :CreateQueue. Boilerplate ships infra code only; you wire your own sqs.MessageHandler.

5. Compile-time DI with google/wire — once you try it you can’t go back

Runtime DI containers feel clever until you spend two hours debugging “nil pointer” because container key resolution failed at startup.

google/wire generates the wiring at compile time. If a provider is missing, the build fails. There’s no reflection, no runtime overhead, and the generated file is grep-able.

//go:build wireinject

func InitializeServer(cfgPath string) (*ServerApp, func(), error) {
    wire.Build(
        config.Load,
        ProvideMongoConfig,
        database.NewClient,
        user.ProviderSet,           // one line wires the whole feature
        role.ProviderSet,
        ProvideRouter,
        wire.Struct(new(ServerApp), "*"),
    )
    return nil, nil, nil
}

make wire regenerates wire_gen.go. CI fails on drift — so a contributor adding a feature without running make wire gets blocked.

The trade-off: there’s a learning curve. But after the first feature, every subsequent addition follows the same ProviderSet pattern and Claude Code (or any agent) can copy it.

Which brings me to —

6. The vibe-coding angle: CLAUDE.md + AGENTS.md

If you’ve used Claude Code, Cursor, Aider, or Windsurf agent mode, you know they all read the repo for context. Most repos give them nothing useful — agent ends up cargo-culting whatever pattern it finds first.

I committed two files specifically for AI agents:

  • CLAUDE.md (215 lines) — commands, architecture, coding conventions, package boundaries, “Adding a feature” checklist
  • AGENTS.md — multi-agent compat redirect (Cursor and Aider both read this convention)
## Coding Conventions

**Naming**
- Interfaces: no `I` prefix. `UserService` (interface) / `userService` (impl)
- Constructors `NewUserService` return the interface
- Errors: sentinel `ErrXxx` in feature-local `errors.go`
...

## Adding an SQS-consuming feature

1. Create `internal/<feature>/event_handler_sqs.go` implementing `sqs.MessageHandler`
2. Replace `ProvideSQSHandler` in `cmd/worker/providers.go`
3. Add `<feature>.NewSQSEventHandler` to `cmd/worker/wire.go` ProviderSet
4. `make wire mocks test`

The payoff is real: I can paste a Linear ticket into Claude Code, and it produces a 80%-correct PR because it knows the repo’s conventions, the test patterns, and which file to edit. The agent doesn’t waste context discovering — the discovery is one file.

This is a controversial design choice. Some people don’t want their repo “locked into” Claude. But CLAUDE.md is just markdown — re-readable by humans, ignorable by anyone who doesn’t use AI. Cost: 215 lines. Benefit: agents move from “best-guess” to “follows your conventions”.

7. The boring stuff you wish someone had written for you

Things in the template you’d otherwise reinvent on day 4 of your project:

  • /healthz (liveness) vs /readyz (readiness) with cached checkers and fail-after-N hysteresis so a transient blip doesn’t flap your load balancer.
  • SSRF-safe HTTP client (httpclient.NewExternalClient) — custom Dialer rejects loopback, RFC1918, link-local, and AWS metadata IPs (169.254.169.254). The default for any non-hardcoded URL.
  • http.Server timeouts wired correctly (ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout) — these CANNOT be replaced by handler-level timeouts because body read happens before the handler.
  • Middleware ordering documented with rationale:
  otelgin → Recovery → RequestID → RequestLogger → RateLimit → BodyLimit → Timeout → CORS → handler
  • Cursor pagination as a platform-level helper — opaque base64(JSON) cursor, generic Page[T] envelope, ClampLimit capped at 100.
  • Shared error envelope {error: {code, message}} via httperror.BadRequest(c, err) so handlers don’t redefine gin.H{"error": err.Error()} 47 times.
  • X-Request-ID propagated through ctx, response headers, and slog fields — debug correlation from client log → server trace.
  • Distroless Docker with healthcheck via the binary’s healthcheck subcommand (because distroless has no curl/wget).
  • make ci runs lint + test + race + govulncheck + gitleaks. The CI workflow blocks merges on any of these.

None of these are revolutionary. Together they save you the first two sprints.

What’s NOT in the template (and why)

I’m allergic to “kitchen sink” boilerplates. Things deliberately excluded:

  • Auth / AuthZ — every team has different requirements (session vs JWT, OPA vs Casbin vs hand-rolled). Adding any choice is a wrong choice for 80% of users.
  • Integration tests with testcontainers — go-kit philosophy: build the integration harness when you have a real feature that needs it, not on speculation.
  • CQRS / event sourcing — demand-driven. If your domain needs it, you know.
  • API versioning gymnastics (v1/, v2/) — the boilerplate has /api/v1 baked in; multi-version is the user’s call.
  • GraphQL — different concern.
  • Frontend — wrong repo.

If you need any of these, the template is a starting point, not a finished product.

How to use it

# 1. Click "Use this template" on GitHub, or:
git clone https://github.com/rock288/go-mongo-boilerplate my-service
cd my-service

# 2. Rename the module path (everywhere it appears)
NEW=github.com/<your-org>/<your-repo>
OLD=github.com/rock288/go-mongo-boilerplate
go mod edit -module=$NEW
grep -rl "$OLD" --include='*.go' --include='*.yml' --include='*.yaml' --include='Makefile' --include='*.md' . 
  | xargs perl -pi -e "s|$OLD|$NEW|g"
make wire && make mocks

# 3. Start your first feature
make scaffold name=Order
# → internal/order/ ready; edit model.go, dto.go, wire it up

# 4. Run
docker-compose up -d
make migrate-up
make run                 # http://localhost:8002

make ci should be green. If it isn’t, that’s a bug — open an issue.

What I’d like feedback on

  • Is internal/middleware/ cleanly separated, or should it move under internal/platform/httpserver/middleware/?
  • The SQS app-level retry queue costs an extra SendMessage per failure compared to native SQS redrive. I chose it for Kafka-symmetry. Worth the cost?
  • MessageDelaySeconds 900s cap forces clamping. For backoffs >15min I think the right answer is “use Step Functions” — but it’s not built in. Should it be?
  • CLAUDE.md + AGENTS.md — pro or con for a public template? I’ve heard both.

Open an issue if you have opinions or — better — a PR.

Links

If this saved you an afternoon, leave a star or share with a teammate who’s about to start their seventh Go service.

If you spot a bug, an outdated dep, or a convention I should fight you about, open an issue. The repo gets better when people argue.

NVIDIA Q1 FY2027 Earnings Preview — 5 Signals the Market May Be Missing

NVIDIA reports Q1 FY2027 earnings on May 20, 2026, after market close. The consensus expects approximately $78.1-78.8 billion in revenue and $1.74 EPS, with Citi and Wells Fargo running slightly higher at ~$80B and $1.79 respectively.

The stock closed at ~$225 on the most recent trading day, roughly a 27x forward P/E on FY2027 estimates. This is not a distressed entry point. It is a thesis-testing moment.

Below are 5 signals to watch that go beyond the headline beat-or-miss narrative. Each maps to a specific structural claim about NVIDIA’s position in the AI infrastructure stack, and each has a falsification trigger that would challenge that claim.

1. Purchase Commitments: Are They Still Rising?

NVIDIA’s supply-related purchase commitments rose from $50.3 billion to $95.2 billion between Q3 and Q4 FY2026, nearly doubling in a single quarter. This is not optional inventory building. It is NVIDIA aggressively locking in component supply for constraints it believes are structural.

What to watch: If commitments rise further in Q1, NVIDIA is deepening its supply chain lock-in through at least 2027. If they flatten, either supply is easing (bullish for margins) or suppliers have hit allocation limits (bearish for revenue growth).

Falsification trigger: A flat or declining commitment trajectory would suggest NVIDIA sees peak demand behind it.

2. Optical Interconnect Mentions

On May 6, NVIDIA announced a $500 million partnership with Corning (GLW) — three new US optical factories, 10x capacity increase, and a warrant structure giving NVIDIA up to 15 million shares at $180. This follows $2B+ purchase commitments each to Lumentum (LITE) (which reported $808M revenue, +90% YoY on May 5) and Coherent (COHR) ($1.81B, +21% YoY on May 6).

Combined, over $4.7B+ was committed to the optical supply chain in 10 weeks across three independent layers: passive fiber (Corning), active components (LITE, COHR), and co-packaged optics (Ayar Labs, ~$155M NVIDIA portion).

What to watch: If management references Corning, Lumentum, or Coherent by name on the call, it validates the thesis that optical interconnect is the next binding constraint beyond HBM memory. Silence on optical supply is a missed signal.

Falsification trigger: If optical supply is described as “secured” or “no longer a concern,” the bottleneck thesis for the photonics layer weakens meaningfully.

3. Blackwell Margin Trajectory

The Street is fixated on gross margins during the Blackwell ramp. The concern is that the more complex B200/B300 packaging (CoWoS-L) compresses margins compared to the simpler H100/H200 designs.

What to watch: The directional trajectory matters more than the absolute number. Sequential margin expansion indicates the ramp is absorbing complexity costs. Compression suggests the packaging premium is permanent.

The deeper signal: NVIDIA’s gross margin has been the most-watched metric for 8 consecutive quarters. The market has already priced in margin compression, so an in-line or better margin print removes a major overhang. A miss amplifies the ASIC-competition narrative.

Context: NVIDIA pre-booked approximately 60% of TSMC’s total 2026 CoWoS output (per Morgan Stanley), with demand of ~700,000 wafers. CoWoS is the packaging constraint. If margins hold despite this capacity scramble, the GPU economics thesis is intact.

4. Inference Mix and Agent Workload Commentary

The AI market narrative shifted in 2026 from “training is everything” to “inference is the growth vector.” Jensen Huang’s GTC keynote emphasized AI factories as long-running inference infrastructure, not just training clusters. Agentic workloads — autonomous systems that chain multiple model calls per task — compound inference demand beyond what chat-era projections captured.

What to watch: Any qualitative commentary about inference workload growth, token demand trajectories, or agentic infrastructure spend. If management quantifies inference as a growing share of data center revenue, it supports the thesis that AI compute demand has structural legs beyond model training.

Falsification trigger: If inference growth is described as “migrating to edge devices” or “handled by CPU-based systems,” the GPU-inference thesis weakens. If there is silence on this topic entirely, the market may be overestimating inference demand relative to what the company sees.

5. ASIC Competition Framing

The most credible competitive threat to NVIDIA is custom hyperscaler ASICs: Google’s TPU 8t/8i (April 22 launch, Anthropic committed to 1M TPU v7 chips), Amazon’s Trainium 2, and Meta’s MTIA (accelerating with Broadcom through 2029). Industry analysts project custom ASIC shipments growing significantly faster than GPU shipments in 2026 as hyperscalers vertically integrate.

However, every ASIC still needs HBM memory, optical interconnect, and CoWoS packaging — all of which NVIDIA has pre-booked at scale. ASIC growth at the margin does not necessarily mean NVIDIA loses revenue. It means the total AI compute pie is growing, and NVIDIA captures the GPU slice while participating in the broader ecosystem via supply chain positioning.

What to watch: How management frames ASIC competition. If they dismiss it as irrelevant, that would suggest they are not tracking the custom silicon trend. If they acknowledge it and frame NVIDIA’s counter-position (CUDA moat, NVLink, ecosystem), that signals clear-eyed strategy.

Falsification trigger: A hyperscaler announcing that their internal chip has reached >50% utilization across their own AI workload would signal structural erosion. Not expected this quarter.

Putting It Together

The standard earnings framework (beat, miss, guide, P/E) tells you how the market feels about NVIDIA today. The 5 signals above test whether NVIDIA’s structural position is improving or eroding.

Signal What It Tests Bullish Bearish
Purchase commitments Supply chain conviction Rising Flat/falling
Optical mentions Bottleneck migration thesis Named by management Not discussed
Blackwell margins Ramp economics Expanding Compressing
Inference mix Demand durability Quantified growth Silent/edge-focused
ASIC framing Competitive awareness Acknowledged + countered Dismissed

The Durability Curve framework rates NVIDIA as a Law I (Bottleneck Migration) and Law II (Difficulty Is Load-Bearing) play. The falsification triggers above test both laws. Through this lens, May 20 is not about whether NVIDIA beats by $1B. It is about whether the structural evidence supports or challenges the durability thesis.

This analysis is derived from the **Durability Curve* research framework, a systematic approach to identifying AI infrastructure bottlenecks before they are priced. The full 36-page NVIDIA Q1 FY2027 earnings research report with detailed falsification triggers, supply chain signal verification across all 5 layers, and options positioning framework is available at:*

📄 NVIDIA Q1 FY2027 Earnings Research Report (36 pages, £9)

Follow @durabilitycurve on Mastodon for real-time signal monitoring during the earnings call. Free weekly analysis at harryfloyd.substack.com.

Not financial advice. All data points verified against public sources as of May 17, 2026. Verify independently before making investment decisions.

Clipboard Monitor + Gemini in a Tauri App — Building a Smarter Dev Tool

All tests run on an 8-year-old MacBook Air.
All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.
HiyokoHelper monitors the clipboard and optionally sends content to Gemini for analysis. It sounds simple. The implementation has specific gotchas.
Here’s what I learned.

Clipboard monitoring in Tauri
There’s no built-in file-watcher equivalent for the clipboard. You poll.
rustuse tauri_plugin_clipboard_manager::ClipboardExt;
use std::time::Duration;
use tokio::time::interval;

async fn watch_clipboard(handle: AppHandle) {
let mut last = String::new();
let mut ticker = interval(Duration::from_millis(500));

loop {
    ticker.tick().await;
    if let Ok(current) = handle.clipboard().read_text() {
        if current != last && !current.is_empty() {
            last = current.clone();
            handle.emit("clipboard-changed", current).ok();
        }
    }
}

}
500ms polling is fast enough to feel responsive. Lower than 200ms and you’re burning CPU for no user benefit.

What to do with clipboard content
For HiyokoHelper, clipboard content goes to Gemini for command explanation and danger detection. The flow:

Clipboard changes
Frontend receives clipboard-changed event
User clicks “Analyze” (or auto-analyze is on)
Content sent to Gemini with a structured prompt
Response shown in the UI

Auto-analyze on every clipboard change is too aggressive. Users copy passwords, personal data, sensitive content. Analyze on demand, not automatically.

The dangerous content problem
Terminal error messages and shell commands are the primary use case. But users also copy:

Passwords
API keys
Personal information
Code with secrets in it

Before sending to Gemini: strip obvious secrets. API keys (long alphanumeric strings), password manager output, anything that looks like a credential.
rustfn looks_like_secret(text: &str) -> bool {
// Long random strings
let has_long_random = text.split_whitespace()
.any(|w| w.len() > 30 && w.chars().all(|c| c.is_alphanumeric()));

// Common secret patterns
let has_secret_pattern = text.contains("sk-") 
    || text.contains("AIza")
    || text.contains("ghp_");

has_long_random || has_secret_pattern

}
Not perfect. Good enough to avoid the most obvious cases.

The history cache
Store clipboard history in SQLite with timestamps. Let users browse, search, and pin items.
Don’t store everything forever. Cap at 100 items. Auto-delete items older than 7 days. Clipboard history that grows unbounded is a privacy and storage problem.

The verdict
Clipboard monitoring is polling. Gemini integration needs explicit user action, not auto-send. History needs a retention policy.
The useful part — AI analysis of terminal errors and commands — is genuinely useful once you get the UX right.

If this was useful, a ❤️ helps more than you’d think — thanks!
Hiyoko PDF Vault → https://hiyokoko.gumroad.com/l/HiyokoPDFVault
X → @hiyoyok