Boost Your Agents with MCPs – Launch First Real-world Agentic App

Why MCP?

MCP stands for Model Context Protocol. Think of it as a standardised USB-C port for AI tools. Instead of every AI application inventing its own plugin system, MCP defines a common interface: a server exposes tools, a client (our agent here) discovers and calls them. The beauty is that the same MCP server can be reused by Claude Desktop, by a Strands agent, by any MCP-compatible client — without changing a single line of server code. This is the interoperability that the ecosystem has been missing. Here, we use FastMCP, a Python library that reduces an MCP server to almost nothing:

from mcp.server import FastMCP

mcp = FastMCP("Calculator Server")
@mcp.tool(description="Add two numbers together")
def add(x: float, y: float) -> float:
    return x + y
if __name__ == "__main__":
    mcp.run(transport="stdio")

That is a fully functional MCP server. The decorator @mcp.tool is all you need to expose a function to any MCP client.

Review: Our Analyst (Sub-agent)

We start with creating our first toolkit: an analyst. Running two terminals — one for the server, the other for the client — we immediately see the protocol in action.

The client (our agent here) connects, discovers the available tools (addition, subtraction, multiplication, and division), and calls them through the protocol (MCP). The agent calls a function without knowing whether the toolkit lives in a separate process or not.

The toolkit definitely resides within a server. The client (our agent here) receives a structured description and autonomously decides when and how to invoke the tool. No prompt engineering is needed to explain what the addition does.

Review: Our Weather Forecaster (Sub-agent)

The second toolkit is to introduce a weather forecaster that speaks the National Weather Service API.

The weather forecaster on a server exposes three tools:

  • get_alerts(state) – active weather alerts by US state code
  • get_forecast(latitude, longitude) – multi-period forecast for a location
  • get_current_weather(latitude, longitude) – live conditions from the nearest observation station

The implementation of the toolkit is fully async, using httpx for HTTP calls. We can call any external API, read from a database, run a subprocess – whatever our agent needs. The MCP layer is one communication contract between the agent (client) and a server where toolkits reside.

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location."""
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)
    # format and return

Supervisor Agent (Head)

Now we connect the dots. A supervisor-mode Strands agent built upon the Amazon Bedrock (Claude Sonnet) is given access to controlling multiple toolkits simultaneously via MCP. When we ask the supervisor something like: “What should I have for lunch given today’s weather in New York?”, the supervisor will leverage the magic power of MCP to pass along the command to different toolkits (sub-agents).

The supervisor figures out that s/he needs to:

  1. Calls the weather forecaster to get current conditions in New York
  2. Reasons which food delivery service makes sense in terms of the current temperature and traffic conditions
  3. Returns a recommendation

Now, the agent is not just completing the prompt texts. Instead, it is orchestrating tool calls, chaining results, and synthesising an answer. The Strands SDK handles the entire tool call loop:

from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient

agent = Agent(
    model=BedrockModel(model_id="anthropic.claude-sonnet-4-5"),
    tools=[*calculator_tools, *weather_tools],
)
response = agent("What should I eat for lunch in New York today?")

Build A Text Analyzer (Sub-agent) From Scratch

We can build a text analyzer to shorten the length of a URL. More importantly, we can internalize the URL pattern.

The server exposes four tools: shorten_url, expand_url, get_url_stats, and analyze_text. The implementation uses an in-memory JSON store (with a note that production code would use a real database). Here is the shortener:

@mcp.tool(description="Shorten a long URL into a compact format")
def shorten_url(url: str, custom_code: str = None) -> Dict:
    content = f"{url}{datetime.now().isoformat()}"
    short_code = hashlib.md5(content.encode()).hexdigest()[:8]
    # store and return
    return {"short_url": f"short.ly/{short_code}"}

Key Takeaway: Any Python function can become an AI tool in minutes. Have a legacy internal API? Wrap it. Have a spreadsheet formula? Wrap it. The barrier is almost zero.

Launch Our Agentic App (Research Analyzer) via Bedrock AgentCore

From the development, we have our agent access toolkits via MCP locally over the stdio transport. On the other hand, the real production systems need:

  • Persistent endpoints accessible over HTTPS
  • IAM-based authentication (no open endpoints)
  • Container packaging for reproducible deployments
  • Auto-scaling based on load

Amazon Bedrock AgentCore Runtime handles all of this. The deployment script is deceptively short:

from bedrock_agentcore_starter_toolkit import Runtime

agentcore_runtime = Runtime()
response = agentcore_runtime.configure(
    entrypoint="text_utils_server.py",
    auto_create_execution_role=True,
    auto_create_ecr=True,
    requirements_file="requirements.txt",
    protocol="MCP",
    agent_name="text_utils_mcp",
)
launch_result = agentcore_runtime.launch()
print(f"Agent ARN: {launch_result.agent_arn}")

Under the hood, the bedrock_agentcore_starter_toolkit:

  • Packages your server and its dependencies into a Docker container
  • Pushes it to ECR (auto-created if it does not exist)
  • Creates an IAM execution role with least-privilege permissions
  • Deploys to AgentCore Runtime and returns a live ARN

After deployment (around 3–5 minutes), we update our agentic App configuration to point to the remote endpoint.

The remote MCP server is protected by AWS IAM via SigV4 signatures — the same signing mechanism used by every other AWS API. Here, we include a custom transport class that handles this transparently:

class SigV4HTTPXAuth(httpx.Auth):
    def __init__(self, credentials, service, region):
        self.signer = SigV4Auth(credentials, service, region)

    def auth_flow(self, request):
        aws_request = AWSRequest(
            method=request.method,
            url=str(request.url),
            data=request.content,
            headers=dict(request.headers),
        )
        self.signer.add_auth(aws_request)
        request.headers.update(dict(aws_request.headers))
        yield request

This extends HTTPX’s auth interface and plugs directly into the MCP StreamableHTTPTransport. Our agentic App continues to call tools with the exact same code – authentication is completely transparent at the transport layer. No API keys in environment variables, no token rotation to manage.

Once the remote server is alive, activating it in the agentic app is a toggle:

Putting Everything Altogether – Our Agentic App (Research Analyzer)

With the full stack in place — local servers, remote AgentCore server, SigV4 auth — our agentic app that combines all tools is now formed:

The “Research Analyzer” can:

  • Fetch and summarise web content
  • Analyse and transform text (via the remote AgentCore server)
  • Shorten URLs from its research
  • Check the weather for context-aware suggestions

All from natural language. All with tools that any developer could have written in an afternoon.

The Multi-Tool Orchestration Layer

Now, let’s explore advanced implementation patterns — how to structure agents that manage many tools gracefully, handle tool failures, and maintain context across a conversation.

A few things worth highlighting from main.py:

  • Tool cache with TTL: tools are loaded once at startup and refreshed on demand, avoiding re-negotiation on every request
  • Session-scoped agents: each user session gets its own agent instance with isolated memory
  • Structured logging per server: each MCP server has its own log stream, invaluable for debugging multi-server interactions
def refresh_tools_cache():
    global cached_tools, tools_last_updated
    cached_tools = mcp_manager.get_all_tools(active_only=True)
    tools_last_updated = datetime.now()

This pattern, loading tools once and reusing them, becomes critical at scale. Negotiating the MCP protocol on every request adds latency; caching eliminates it.

Key Takeaways

  • MCP is the right abstraction. The separation between tool definition (server) and tool consumption (client/agent) is clean and powerful. It enables a genuine ecosystem of reusable tools.

  • FastMCP makes it trivial to start. Going from idea to working MCP server takes minutes, not days. The barrier to contributing tools to the ecosystem is essentially zero.

  • AgentCore closes the last mile. The hardest part of agentic AI in production is not the model or the tools — it is infrastructure: endpoints, auth, containers, scaling. AgentCore Runtime handles all of it with three lines of configuration.

  • SigV4 is the right auth for AWS-native stacks. No secret management overhead, no token rotation, automatic credential refresh. If your infrastructure is already on AWS, this is the obvious choice.

🚀 Stop Rewriting Components — Convert React to Vue Automatically

What if switching frameworks didn’t mean rewriting everything from scratch?

Frontend developers are constantly facing this problem:

  • You start with React, then your team (or client) prefers Vue
  • You want to reuse logic across ecosystems
  • Or you’re exploring frameworks but hate duplicating effort

So I built something to challenge that.

🔥 Introducing: cross-framework

A source-to-source compiler that converts:

👉 React (JSX / TSX) → Vue 3 (Single File Components)

And no — this is not regex hacks or AI guessing.

This is a real compiler pipeline:

  • Parse code using Babel
  • Transform into an intermediate representation (IR)
  • Emit clean Vue 3 <script setup> + <template>

🎥 Demo (see it in action)

Watch the demo here:
https://x.com/i/status/2037575942160122244

🧠 Why this matters

Switching frameworks today is expensive:

  • Rewriting components manually
  • Risk of introducing bugs
  • Losing consistency across codebases

AI tools try to solve this, but:

  • ❌ Results are inconsistent
  • ❌ Require prompt engineering
  • ❌ Break structure in complex cases
  • ❌ Come with ongoing pricing plans

This project focuses on:
✔ deterministic output
✔ structured transformations
✔ developer-first control

⚙️ How it works

CLI usage

npm install cross-framework
npx cross-framework convert --from react --to vue ./path/Component.tsx

This generates:

Component.vue

You can also specify an output directory with --out-dir.

Programmatic API

import { convertCode } from "cross-framework"

const vueCode = convertCode({
  from: "react",
  to: "vue",
  sourceCode: reactSource,
  filename: "Component.tsx",
})

🧩 What it supports

✅ React Features

  • Function components (default / named exports)
  • memo and forwardRef

✅ Hooks → Vue equivalents

  • useState → ref
  • useRef → ref
  • useMemo → computed
  • useEffect → watch, watchEffect, onMounted

✅ JSX → Vue Template

  • Props & spreads
  • Events
  • Lists (map)
  • Conditionals
  • Fragments

All transformed into clean Vue 3 template syntax:

  • className → class / :class
  • JSX → template IR → Vue output

⚠️ Current limitations

Like any early-stage compiler, there are some constraints:

  • Imports are not rewritten
  • Multi-component files may need manual adjustments
  • Design-system-heavy props might need cleanup

🌍 Try it yourself

🧪 Playground:
https://cross-framework.netlify.app/

📚 Documentation:
https://cross-framework-doc.netlify.app/

🧭 The vision

This is just the beginning.

Next steps:

  • Support more frameworks (Angular, Svelte, etc.)
  • Convert entire projects, not just components
  • Build a universal transformation pipeline for frontend code

Imagine:

Write once → run anywhere (React, Vue, more…)

❤️ Support the project

If you believe in this direction and want to see it grow:

👉 https://github.com/sponsors/parsajiravand

🤝 Join the team

I’m looking for developers who want to build something ambitious in the frontend ecosystem.

If you’re interested in contributing long-term:

📩 [email protected]

🧠 Final thought

We’ve spent years choosing frameworks.

Maybe it’s time to stop choosing…
and start abstracting the boundaries between them.

#webdevelopment #frontend #reactjs #vuejs #javascript #typescript #opensource #programming #softwareengineering #developer #coding #devtools #saas #ai #buildinpublic #100daysofcode #github #webdev #frontenddeveloper #techinnovation

API Rate Limiting Strategies for Multi-Account Web Scrapers

A practical guide to handling rate limits, token buckets, and request throttling when managing multiple accounts across web automation pipelines.

If you’ve built any serious scraping or multi-account automation system, you’ve hit rate limits. And if you’ve hit rate limits while managing multiple accounts simultaneously, you know how quickly things spiral — one misconfigured request queue can burn through a dozen accounts before you even notice.

I’ve spent considerable time building and debugging automation pipelines that operate across multiple accounts on the same platform. The lesson I keep coming back to: most rate limiting failures aren’t caused by too many requests. They’re caused by poor architecture decisions made before the first request is even sent.

This article breaks down the strategies that actually work, including code patterns you can adapt immediately.

Understanding What You’re Actually Fighting

Before writing any throttling logic, it’s worth understanding how modern platforms enforce limits. Most APIs enforce rate limiting at multiple layers simultaneously:

IP-level throttling: The platform counts requests from your IP address. Exceeding a threshold triggers a temporary or permanent block.

Account-level throttling: Separate from IP, each authenticated account has its own quota. Heavy-use accounts get flagged even if your IP is clean.

Behavioral throttling: Pattern-based detection. If all your accounts perform the same actions in the same sequence at the same intervals, the platform flags it as automated regardless of raw request counts.

Session fingerprint correlation: Even with different IPs and accounts, if the underlying browser or client environment is identical across sessions, detection systems correlate and flag them collectively.

Understanding which layer is throttling you determines which solution to apply. Blindly throwing more proxies at an account-level problem, or increasing delays when the issue is fingerprint correlation, wastes time and resources.

Strategy 1: Token Bucket Implementation

The token bucket algorithm is the most effective pattern for smooth, consistent rate-limiting compliance. Instead of hard-stopping when you hit a limit, you pre-regulate outgoing requests.

class TokenBucket {
  constructor(capacity, refillRate) {
    this.capacity = capacity;        // Max tokens (burst limit)
    this.tokens = capacity;          // Current available tokens
    this.refillRate = refillRate;    // Tokens added per second
    this.lastRefill = Date.now();
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    const tokensToAdd = elapsed * this.refillRate;
    this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }

  async consume(tokens = 1) {
    this.refill();

    if (this.tokens >= tokens) {
      this.tokens -= tokens;
      return true;
    }

    // Wait until enough tokens are available
    const waitTime = ((tokens - this.tokens) / this.refillRate) * 1000;
    await new Promise(resolve => setTimeout(resolve, waitTime));
    this.tokens -= tokens;
    return true;
  }
}

// One bucket per account
const accountBuckets = new Map();

function getBucket(accountId, rateLimit = 10) {
  if (!accountBuckets.has(accountId)) {
    // 10 requests capacity, 10 refills per second
    accountBuckets.set(accountId, new TokenBucket(rateLimit, rateLimit));
  }
  return accountBuckets.get(accountId);
}

async function makeRequest(accountId, url, options) {
  const bucket = getBucket(accountId);
  await bucket.consume();

  // Proceed with actual request
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${getAccountToken(accountId)}`
    }
  });

  return response;
}

The key advantage here is per-account isolation. Each account gets its own token bucket, so a burst of requests against one account doesn’t drain capacity for others.

Strategy 2: Exponential Backoff with Jitter

When you do hit a rate limit (and you will), how you recover matters as much as how you throttle. Naive implementations retry immediately or after a fixed delay — which often means dozens of accounts all retrying simultaneously, amplifying the problem.

async function requestWithBackoff(fn, options = {}) {
  const {
    maxRetries = 5,
    baseDelay = 1000,      // 1 second base
    maxDelay = 60000,      // 60 second cap
    jitterFactor = 0.3     // 30% random jitter
  } = options;

  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      const result = await fn();

      // Handle explicit rate limit responses
      if (result.status === 429) {
        const retryAfter = result.headers.get('Retry-After');
        const delay = retryAfter
          ? parseInt(retryAfter) * 1000
          : calculateBackoff(attempt, baseDelay, maxDelay, jitterFactor);

        console.log(`Rate limited. Waiting ${delay}ms before retry ${attempt + 1}`);
        await sleep(delay);
        attempt++;
        continue;
      }

      return result;

    } catch (error) {
      if (attempt === maxRetries - 1) throw error;

      const delay = calculateBackoff(attempt, baseDelay, maxDelay, jitterFactor);
      await sleep(delay);
      attempt++;
    }
  }
}

function calculateBackoff(attempt, baseDelay, maxDelay, jitterFactor) {
  const exponential = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
  const jitter = exponential * jitterFactor * Math.random();
  return Math.floor(exponential + jitter);
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

The jitter component is critical for multi-account scenarios. Without it, all accounts recovering from the same rate limit event retry at near-identical intervals — creating synchronized traffic spikes that often trigger the limit again immediately.

Strategy 3: Priority Queue with Account Rotation

When managing many accounts, you need intelligent request scheduling — not just throttling. A priority queue allows you to interleave requests across accounts in a way that looks organic.

class MultiAccountScheduler {
  constructor() {
    this.queues = new Map();       // accountId -> request queue
    this.cooldowns = new Map();    // accountId -> available timestamp
    this.running = false;
  }

  enqueue(accountId, task, priority = 1) {
    if (!this.queues.has(accountId)) {
      this.queues.set(accountId, []);
    }

    const queue = this.queues.get(accountId);
    queue.push({ task, priority, addedAt: Date.now() });

    // Sort by priority descending, then by insertion time ascending
    queue.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);

    if (!this.running) {
      this.run();
    }
  }

  getAvailableAccount() {
    const now = Date.now();

    for (const [accountId, queue] of this.queues.entries()) {
      if (queue.length === 0) continue;

      const cooldownUntil = this.cooldowns.get(accountId) || 0;
      if (now >= cooldownUntil) {
        return accountId;
      }
    }

    return null;
  }

  setCooldown(accountId, delayMs) {
    this.cooldowns.set(accountId, Date.now() + delayMs);
  }

  async run() {
    this.running = true;

    while (true) {
      const accountId = this.getAvailableAccount();

      if (!accountId) {
        // All accounts on cooldown - find shortest wait
        const minCooldown = Math.min(...this.cooldowns.values());
        const waitTime = Math.max(0, minCooldown - Date.now());

        if (waitTime > 0) {
          await sleep(waitTime);
        }
        continue;
      }

      const queue = this.queues.get(accountId);
      const { task } = queue.shift();

      try {
        await task();
        // Add small random delay between tasks for same account
        this.setCooldown(accountId, 500 + Math.random() * 1500);
      } catch (error) {
        if (error.status === 429) {
          // Longer cooldown on rate limit hit
          this.setCooldown(accountId, 30000 + Math.random() * 30000);
        }
      }

      // Small yield between accounts
      await sleep(50 + Math.random() * 100);
    }
  }
}

The random delays between tasks are intentional. Perfectly timed intervals are a detection signal — human-like variation is far harder to distinguish from organic usage.

Strategy 4: Response Header Monitoring

Most APIs communicate their rate limit state through response headers. Ignoring these is like driving without watching your speedometer.

class RateLimitMonitor {
  constructor() {
    this.state = new Map(); // accountId -> rate limit state
  }

  parseHeaders(accountId, headers) {
    const state = {
      limit: parseInt(headers.get('X-RateLimit-Limit') || '0'),
      remaining: parseInt(headers.get('X-RateLimit-Remaining') || '0'),
      reset: parseInt(headers.get('X-RateLimit-Reset') || '0'),
      retryAfter: parseInt(headers.get('Retry-After') || '0')
    };

    this.state.set(accountId, state);

    // Pre-emptive throttle when approaching limit
    const usagePercent = 1 - (state.remaining / state.limit);

    if (usagePercent > 0.85) {
      console.warn(`Account ${accountId}: ${Math.round(usagePercent * 100)}% of rate limit used`);
      return 'slow_down';
    }

    if (state.remaining === 0) {
      const waitMs = (state.reset * 1000) - Date.now();
      return { action: 'pause', waitMs: Math.max(0, waitMs) };
    }

    return 'ok';
  }

  getAccountState(accountId) {
    return this.state.get(accountId) || null;
  }
}

Catching the 85% threshold and slowing down before hitting the actual limit prevents the 429 response entirely, which keeps accounts healthier over time.

The Layer Most Developers Overlook

All of the strategies above address request-level rate limiting. But for multi-account automation specifically, there’s a layer above this that gets ignored until accounts start disappearing.

Modern platforms correlate sessions through browser environment signals — not just IP and account credentials. Canvas fingerprinting, WebGL output, audio context signatures, navigator properties. If every account in your pool runs through the same headless browser instance with default settings, detection systems see one actor operating multiple accounts, regardless of how well-regulated your request rates are.

This is why production multi-account systems pair request management with isolated browser environments. Tools like BitBrowser allow you to assign each account a unique, persistent browser fingerprint that survives across sessions. Combined with per-account proxies and the throttling architecture above, each account presents as a genuinely independent user — at both the network level and the browser environment level.

The integration pattern is straightforward: BitBrowserexposes a local API that lets you open profiles programmatically, so your scheduler can spin up the right profile before making requests for a given account, then hand off to whatever automation library you’re using downstream.

Putting It Together: A Unified Architecture

Here’s how these strategies compose into a production-ready system:

class MultiAccountScraper {
  constructor(accounts, config = {}) {
    this.scheduler = new MultiAccountScheduler();
    this.monitor = new RateLimitMonitor();
    this.accounts = accounts;
    this.config = {
      defaultRateLimit: config.defaultRateLimit || 10,
      backoffOptions: config.backoffOptions || {}
    };

    // Initialize token buckets per account
    accounts.forEach(account => {
      getBucket(account.id, this.config.defaultRateLimit);
    });
  }

  async scrape(accountId, url) {
    return new Promise((resolve, reject) => {
      this.scheduler.enqueue(accountId, async () => {
        const result = await requestWithBackoff(async () => {
          const bucket = getBucket(accountId);
          await bucket.consume();

          const response = await fetch(url, {
            headers: { 'Authorization': `Bearer ${getToken(accountId)}` }
          });

          const rateLimitStatus = this.monitor.parseHeaders(accountId, response.headers);

          if (rateLimitStatus?.action === 'pause') {
            await sleep(rateLimitStatus.waitMs);
          }

          return response;
        }, this.config.backoffOptions);

        resolve(result);
      });
    });
  }
}

// Usage
const scraper = new MultiAccountScraper(accounts);
const results = await Promise.all(
  targetUrls.map((url, i) =>
    scraper.scrape(accounts[i % accounts.length].id, url)
  )
);

Common Mistakes Worth Avoiding

Sharing a single rate limit counter across accounts. Each account has independent limits. Pool-level counting masks individual account exhaustion until it’s too late.

Not handling Retry-After headers. When a platform tells you exactly how long to wait, use that value — not a hardcoded backoff.

Identical request intervals. Whether it’s 500ms or 2000ms, perfect regularity is a detection signal. Always add variance.

Treating all rate limit errors the same. A 429 from exhausted quota recovers quickly. A 429 from suspicious behavioral patterns may require longer cooldowns or account rotation.

Conclusion

Rate limiting in multi-account automation is a layered problem. Token buckets handle steady-state throughput. Exponential backoff with jitter handles recovery. Priority queues distribute load intelligently. Response header monitoring gives you early warning before limits are hit.

But beyond the request architecture, the browser environment underneath your automation stack matters just as much. A well-tuned scheduler running through fingerprint-identical sessions will eventually get correlated and flagged regardless of how good your throttling is.

Building the full stack — per-account rate management, proxy assignment, and browser environment isolation — is what separates automation systems that survive long-term from those that don’t.

What rate limiting challenges have you run into building multi-account systems? Have you found patterns that work better than token buckets in specific scenarios? Share in the comments.

Building GigShield AI: Real-Time Insurance for India’s Gig Workers

🌍 The Problem: Invisible Risk in the Gig Economy

India’s gig economy runs on speed and reliability. From food delivery to last-mile logistics, millions of workers ensure that urban life stays convenient.

But there’s a hidden vulnerability.
Gig workers face income instability due to external disruptions:

  • Heavy rainfall
  • Air pollution spikes
  • Traffic congestion
  • Sudden curfews or restrictions

A single bad day can cut 20–30% of daily earnings — and traditional insurance doesn’t help.

Why?

Because it’s:

  • Claim-based
  • Slow
  • Reactive

By the time payouts arrive, the damage is already done.

💡 The Idea: Insurance That Thinks and Acts Instantly

We built GigShield AI, a platform that reimagines insurance using parametric models + real-time AI.

Instead of filing claims, the system works like this:

Disruption detected
↓
Delivery activity drops
↓
AI estimates income loss
↓
Instant payout credited

No forms. No delays. No friction.

This transforms insurance from reactive compensation → proactive protection

⚙️ System Architecture: Built for Real-Time Decisions

GigShield AI follows an event-driven architecture, designed to continuously monitor and react to disruptions.

Core Layers

🧩 Data Ingestion Layer – Streams real-time data from:

  • Weather APIs
  • AQI (Air Quality Index) APIs
  • Traffic data sources
  • News feeds (for curfews, disruptions)

🤖 AI Processing Layer – Processes incoming signals and evaluates:

  • Disruption severity
  • Regional risk levels
  • Expected impact on earnings

⚡ Trigger Engine – The heart of the system:

  • Applies predefined thresholds
  • Validates conditions
  • Instantly triggers payouts

🛡 Fraud Detection Layer – Ensures system integrity using anomaly detection:

  • Identifies suspicious claim patterns
  • Validates GPS and environmental consistency
  • Flags abnormal behavior

🤖 AI Models Powering GigShield

We used multiple ML models to handle different parts of the pipeline.

📊 Risk Prediction Model (Classification)

Model: Random Forest Classifier

Inputs:

  • Rainfall history
  • AQI levels
  • Traffic congestion
  • Seasonal patterns

Output: Risk score (0–1) – This score directly influences premium pricing.

💰 Income Loss Prediction (Regression)

Model: Random Forest Regressor

Example:

  • Normal earnings: ₹1200/day
  • Rain day predicted earnings: ₹400
  • → Estimated loss: ₹800

This becomes the payout amount.

🛡 Fraud Detection Model

Model: Isolation Forest

Detects:

  • Abnormal claim frequency
  • Inconsistent GPS data
  • Mismatch with real-world conditions

Keeps payouts fair and tamper-proof.

📊 Product Experience

We designed the platform for both workers and administrators.

👷 Worker Dashboard

Workers can see:

  • Protected earnings
  • Active insurance coverage
  • AI risk score
  • Real-time disruption alerts
  • Payout history

Includes a simulation mode to test events like heavy rain or pollution spikes.

🧑‍💼 Admin Dashboard

Provides:

  • Total insured workers
  • Active policies
  • Payout analytics
  • Fraud alerts
  • System health metrics

Enables real-time operational monitoring.

🗺 Disaster Prediction Heatmap

One of the most impactful features. Visualizes city-wide risk levels:

🟢 Low risk
🟡 Medium risk
🔴 High risk

Helps:

  • Workers optimize routes
  • Insurers identify high-risk zones

🧠 Challenges We Faced

⚠️ Data Reliability – APIs had inconsistent update intervals → required normalization and smoothing.

📉 Limited Training Data – No structured datasets for gig worker earnings → we had to:

  • Simulate data
  • Use proxy features
  • Engineer realistic patterns

🔐 Fraud Prevention- We needed multi-layer validation to ensure:

  • No false triggers
  • No exploitation of payouts

🏆 What We Built

In this project, we successfully:

  • Built a working parametric insurance prototype
  • Implemented real-time disruption triggers
  • Integrated ML models for prediction
  • Designed a scalable event-driven system
  • Created transparent dashboards

Most importantly:
We proved that insurance can be instant, automated, and intelligent.

🔮 What’s Next

We’re just getting started.

Future improvements:

  • Mobile app with push notifications
  • Graph-based fraud detection models
  • Expansion beyond delivery (ride-sharing, freelancing, etc.)

Insurance shouldn’t wait for problems.
It should react the moment they begin.
GigShield AI is a step toward that future —
where protection is instant, intelligent, and invisible.

Confused About Sync vs Async JavaScript? Read This!

Introduction

JavaScript is one of the most popular programming languages used for building web applications. One of the most important concepts every developer must understand is how JavaScript executes code — specifically, the difference between synchronous and asynchronous behavior.

Understanding this will help you write faster, more efficient, and non-blocking applications.

What is Synchronous JavaScript?

Synchronous JavaScript means that code is executed line by line, in order. Each task must complete before the next one begins.

Key Idea:

One task at a time — blocking execution.

Example:

console.log("First");
console.log("Second");
console.log("Third");

Output:

First
Second
Third

Advantages:

  • Simple and easy to understand
  • Predictable execution order

Disadvantages:

  • Blocks execution
  • Can make applications slow if tasks take time

What is Asynchronous JavaScript?

Asynchronous JavaScript allows tasks to run in the background, without blocking the main thread. This means other code can continue executing while waiting for a task to complete.

Key Idea:

Non-blocking execution — multiple tasks handled efficiently.

Example:

console.log("First");

setTimeout(() => {
  console.log("Second");
}, 2000);

console.log("Third");

Output:

First
Third
Second

Advantages:

  • Faster and more efficient
  • Improves user experience
  • Prevents UI freezing

Disadvantages:

  • Slightly harder to understand
  • Requires handling callbacks or promises

Synchronous vs Asynchronous — Key Differences

Feature Synchronous Asynchronous
Execution One after another Runs in background
Blocking Yes No
Performance Slower for heavy tasks Faster & efficient
Complexity Simple Slightly complex
Use Cases Simple logic APIs, timers, I/O tasks

Real-Life Analogy

Synchronous:

Cooking one dish at a time — you wait until it’s done before starting the next.

Asynchronous:

Ordering food and doing other work while waiting for delivery.

When to Use What?

Use Synchronous:

  • Simple calculations
  • Sequential operations
  • Small scripts

Use Asynchronous:

  • API calls
  • File handling
  • Database operations
  • Timers and delays
  • Network requests

Conclusion

  • JavaScript is synchronous by default
  • Asynchronous programming makes it powerful and efficient
  • Mastering async concepts is essential for modern web development

Give your AI agent a real memory in 50 lines of Python

Your AI agent is brilliant for exactly one conversation. Then it forgets everything.

It doesn’t remember that the user prefers dark mode. It can’t recall that it already solved this exact problem last Tuesday. It has no idea which approach worked and which one failed.

Most developers fix this by duct-taping a vector store, a Redis cache, and a conversation log together. That’s three services to maintain for something that should be built into the engine.

I wanted a single pip install that gives an AI agent the same three types of memory that cognitive science describes for humans: what it knows (semantic), what it experienced (episodic), and what it knows how to do (procedural). This distinction comes from Endel Tulving’s foundational work (1972) and Larry Squire’s taxonomy of memory systems (2004), and it has been successfully implemented in cognitive architectures like SOAR and ACT-R for decades.

So I built it into VelesDB.

The problem with “memory” in AI agents today

Most agent frameworks give you a flat list of messages or a vector store. That’s like giving a human a notebook and calling it memory.

Real memory has structure:

  • Semantic memory is your knowledge base. Facts, concepts, relationships. “Python is interpreted.” “The user’s timezone is UTC+1.”
  • Episodic memory is your autobiography. What happened, when, in what context. “Last Tuesday, the user asked about deployment and I suggested Docker.”
  • Procedural memory is your muscle memory. Skills, workflows, learned behaviors. “When the user asks about bugs, first check the logs, then reproduce the issue.”

LangChain gives you ConversationBufferMemory. That’s episodic at best, and it’s just a list. No semantic search, no temporal queries, no skill learning. If you want all three memory types, you’re stitching together Pinecone + Redis + Postgres – three services, three APIs, three failure modes.

Setup: one line

pip install velesdb

No Docker. No Redis. No Postgres. The entire engine – vector search, graph storage, and the agent memory SDK – ships as a ~3MB native binary inside the Python wheel.

A complete agent memory in 50 lines

Here’s a personal assistant that remembers facts about the user, recalls past conversations, and learns which approaches work:

import velesdb
import time
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
db = velesdb.Database("./agent_brain")
memory = db.agent_memory(384)

def embed(text):
    return model.encode(text).tolist()

# ---- Semantic: store facts the agent learns ----
memory.semantic.store(1, "User prefers dark mode in all applications", embed("User prefers dark mode in all applications"))
memory.semantic.store(2, "User's timezone is Europe/Paris (UTC+1)", embed("User's timezone is Europe/Paris (UTC+1)"))
memory.semantic.store(3, "User is building a RAG pipeline with LangChain", embed("User is building a RAG pipeline with LangChain"))

# ---- Episodic: record what happened during conversations ----
now = int(time.time())
memory.episodic.record(1, "User asked how to chunk PDFs for RAG", now - 86400, embed("User asked how to chunk PDFs for RAG"))
memory.episodic.record(2, "Suggested recursive text splitter, user said it worked", now - 3600, embed("Suggested recursive text splitter, user said it worked"))
memory.episodic.record(3, "User reported slow vector search on 50K vectors", now, embed("User reported slow vector search on 50K vectors"))

# ---- Procedural: learn skills from experience ----
memory.procedural.learn(1, "debug_slow_search", [
    "Check vector count and dimension",
    "Verify HNSW index is built",
    "Profile the query with timing",
    "Consider reducing top_k or adding filters"
], embed("debug slow vector search performance issues"), 0.85)

memory.procedural.learn(2, "help_with_rag", [
    "Ask about data source format (PDF, web, DB)",
    "Recommend chunking strategy based on source",
    "Suggest embedding model for their language",
    "Show how to store and search with VelesDB"
], embed("help user build a RAG retrieval pipeline"), 0.92)

That’s your agent’s brain. Persisted to disk, searchable, and it survives restarts. Now let’s use it.

Querying each memory type

Semantic: “What do I know about this user?”

# The agent needs context about the user before responding
facts = memory.semantic.query(embed("What is the user currently building?"), top_k=3)
for fact in facts:
    print(f"[{fact['score']:.2f}] {fact['content']}")
[0.32] User is building a RAG pipeline with LangChain
[0.11] User's timezone is Europe/Paris (UTC+1)
[0.10] User prefers dark mode in all applications

The agent now knows the user is working with LangChain and RAG before the user even mentions it in this session.

Episodic: “Have we dealt with this before?”

# User says: "My search is slow again"
similar_events = memory.episodic.recall_similar(embed("my vector search is slow"), top_k=2)
for event in similar_events:
    print(f"[{event['score']:.2f}] {event['description']}")
[0.76] User reported slow vector search on 50K vectors
[0.10] User asked how to chunk PDFs for RAG

The agent remembers this exact problem has happened before, with a strong similarity score. Instead of starting from scratch, it can reference past context.

You can also query by time:

# What happened in the last hour?
recent = memory.episodic.recent(limit=5)

# What happened before yesterday?
old_events = memory.episodic.older_than(before=int(time.time()) - 86400, limit=10)

Procedural: “What should I do here?”

# The user mentions slow search. The agent looks up its playbook.
procedures = memory.procedural.recall(embed("vector search is slow, need to debug performance"), top_k=1)
for proc in procedures:
    print(f"Procedure: {proc['name']} (confidence: {proc['confidence']:.0%})")
    for i, step in enumerate(proc['steps'], 1):
        print(f"  {i}. {step}")
Procedure: debug_slow_search (confidence: 85%)
  1. Check vector count and dimension
  2. Verify HNSW index is built
  3. Profile the query with timing
  4. Consider reducing top_k or adding filters

The agent has a learned playbook. And the best part: it gets better over time.

The feedback loop: agents that learn

After the agent follows a procedure and it works, reinforce it:

# The debug_slow_search procedure worked - boost its confidence
memory.procedural.reinforce(1, success=True)

If it failed:

# The procedure didn't help this time
memory.procedural.reinforce(1, success=False)

Confidence scores adjust automatically. Over time, the agent naturally favors procedures that have a track record of working. Confidence scores adjust automatically: +0.1 on success, -0.05 on failure, clamped to [0, 1]. It’s a deliberately simple mechanism. Over time, the agent naturally favors procedures that have a track record of working. The idea of reinforcing procedural knowledge through experience is inspired by cognitive architectures like SOAR and ACT-R, though VelesDB keeps the implementation pragmatic rather than trying to model the full complexity of human skill acquisition.

Real use case: a support agent that gets smarter

Here’s how these three memory types work together in a realistic scenario:

def handle_user_message(memory, message, model):
    """A support agent that uses all three memory types."""
    msg_vec = model.encode(message).tolist()

    # 1. What do I know about this user? (semantic)
    context = memory.semantic.query(msg_vec, top_k=3)

    # 2. Have we seen this problem before? (episodic)
    past = memory.episodic.recall_similar(msg_vec, top_k=3)

    # 3. What's my best playbook for this? (procedural)
    playbooks = memory.procedural.recall(msg_vec, top_k=1, min_confidence=0.5)

    # Build the prompt with real context
    prompt = f"User message: {message}nn"
    if context:
        prompt += "Known facts about this user:n"
        prompt += "n".join(f"- {c['content']}" for c in context) + "nn"
    if past:
        prompt += "Relevant past interactions:n"
        prompt += "n".join(f"- {p['description']}" for p in past) + "nn"
    if playbooks:
        p = playbooks[0]
        prompt += f"Recommended procedure ({p['name']}, {p['confidence']:.0%} confidence):n"
        prompt += "n".join(f"  {i+1}. {s}" for i, s in enumerate(p['steps']))

    return prompt

# Example
prompt = handle_user_message(memory, "My vector search is really slow", model)
print(prompt)

The agent doesn’t just answer the question. It brings the full context: who the user is, what happened before, and what to do about it. That’s the difference between a chatbot and an assistant.

What this replaces

What you need Without VelesDB With VelesDB
Semantic memory Pinecone/Chroma + custom metadata memory.semantic.store()
Episodic memory Redis/Postgres + timestamp queries memory.episodic.record()
Procedural memory Custom code + JSON files memory.procedural.learn()
Infrastructure 3 services, 3 APIs, Docker pip install velesdb
Disk footprint 500MB+ Docker images ~3MB binary, data files only

Limitations (being honest)

VelesDB is a single-node embedded database. It’s not a replacement for a distributed system.

  • Scaling: if your agent needs to handle millions of memories across multiple machines, use a client-server vector database
  • Multi-process: VelesDB locks the database directory, so one process at a time (fine for most agent use cases)
  • Embedding model: you bring your own, VelesDB stores and searches vectors but doesn’t generate them

For the 90% of agent projects that run on a single machine with thousands to hundreds of thousands of memories, this is the entire backend.

Getting started

pip install velesdb
import velesdb

db = velesdb.Database("./agent_brain")
memory = db.agent_memory(384)  # dimension of your embedding model

# Store a fact
memory.semantic.store(1, "User likes Python", [0.1, 0.2, ...])  # 384-dim vector

# Record an event
memory.episodic.record(1, "User asked about Python", int(time.time()), [0.1, ...])

# Learn a procedure
memory.procedural.learn(1, "help_with_python", ["Check version", "Suggest docs"], [0.1, ...], 0.8)

Full docs: velesdb.com/en
GitHub: github.com/cyberlife-coder/VelesDB
Python SDK (MIT): pypi.org/project/velesdb

References

The Agent Memory SDK is grounded in decades of cognitive science research:

  • Tulving, E. (1972). Episodic and Semantic Memory. In E. Tulving & W. Donaldson (Eds.), Organization of Memory (pp. 381-403). Academic Press. The foundational paper that introduced the distinction between semantic memory (general knowledge) and episodic memory (personal experiences).
  • Squire, L.R. (2004). Memory systems of the brain: A brief history and current perspective. Neurobiology of Learning and Memory, 82(3), 171-177. The modern taxonomy that maps declarative (semantic + episodic) and nondeclarative (procedural) memory to distinct brain systems.
  • Anderson, J.R. (1996). ACT-R: A Theory of Higher Level Cognition. Human-Computer Interaction, 12(4), 439-462. The cognitive architecture that models how declarative knowledge compiles into procedural skills through practice. VelesDB’s procedural.reinforce() borrows the core idea (procedures gain or lose confidence with experience) while keeping the implementation simple and predictable.
  • Laird, J.E., Newell, A., & Rosenbloom, P.S. (1987). SOAR: An Architecture for General Intelligence. Artificial Intelligence, 33(1), 1-64. The first cognitive architecture to unify problem-solving, learning, and multiple memory systems in a single agent framework.

I’m building VelesDB as a source-available local-first database for AI applications. If you’re working on AI agents and struggling with memory management, I’d like to hear about your setup. What are you currently using to persist agent state between sessions?

April 2 – AI, ML and Computer Vision Meetup

Join us on April 2 at 9 AM Pacific for the monthly AI, ML and Computer Vision Meetup!

Register for the Zoom

Talks will include:

  • Visual AI at the Edge: Beyond the Model – David Moser at Orella Vision
  • Sanitizing Evaluation Datasets: From Detection to Correction – Nick Lotz at Voxel51
  • Async Agents in Production: Failure Modes and Fixes – Meryem Arik at Doubleword
  • Building enterprise agentic systems that scale – Aman Sardana at Cisco

I Built an All-in-One Local AI App — Chat, Image Gen, and Video Gen in One UI

Last year my desktop looked like a disaster zone. I had an Ollama terminal running in one corner, ComfyUI open in Firefox with like 14 tabs of different workflows, a separate janky video generation tool I found on GitHub that only worked half the time, and a text file where I kept track of which models I had downloaded. Alt-tabbing between all of this was my entire workflow.

One night I was trying to generate an image, refine a prompt with a chat model, then feed that into a video gen pipeline, and I just… stopped. Closed everything. Opened VS Code instead.

I thought: why isn’t there just ONE app for all of this?

So I built one. It’s called Locally Uncensored, and it’s completely open source.

The Problem Nobody Talks About

The local AI space is incredible right now. Ollama made running LLMs stupid easy. ComfyUI is a powerhouse for image generation. There are video gen tools popping up every week.

But they’re all separate things. Separate UIs. Separate configs. Separate browser tabs. Separate terminal windows.

If you’re just casually chatting with an LLM, sure, the Ollama CLI is fine. But the second you want to do anything more — switch personas, generate an image, manage your models — you’re juggling apps like a circus act.

I wanted one window. One UI. Everything local. Everything offline. No API keys, no subscriptions, no data leaving my machine.

What I Actually Built

Locally Uncensored is a desktop-first web app that wraps Ollama and ComfyUI into a single, clean interface. You get:

  • Chat with any Ollama model, with 25+ built-in personas (coding assistant, creative writer, debate partner, you name it — and yeah, you can make your own)
  • Image generation through ComfyUI, without needing to touch ComfyUI’s node editor
  • Video generation in the same UI
  • A model manager so you can pull, delete, and switch models without touching the terminal
  • Dark and light mode because I’m not an animal

Everything runs 100% on your machine. No cloud. No telemetry. No “we updated our privacy policy” emails.

The Tech Stack (for the curious)

I went with React 19 and TypeScript because, honestly, that’s what I know best and I wanted to move fast. Tailwind CSS 4 for styling — I know some people have opinions about Tailwind but it let me iterate on the UI incredibly quickly without context-switching to separate CSS files. Vite 8 for the build tooling because life’s too short for slow dev servers.

The backend talks to Ollama’s API directly and integrates with ComfyUI’s API for image and video generation. No middleware server, no extra layers. It’s as thin as I could make it.

One thing I’m genuinely proud of is how snappy it feels. I’ve used a lot of local AI UIs that feel sluggish or over-engineered. I wanted this to feel like a native app, not a web page pretending to be one.

The Persona System (My Favorite Feature)

Okay, I have to talk about this because it’s the feature that made me actually enjoy using local models day-to-day.

Most chat UIs give you a system prompt box and call it a day. I wanted something more structured. Locally Uncensored ships with 25+ personas out of the box — things like a Python tutor, a sarcastic code reviewer, a Socratic philosophy partner, a creative writing coach.

But the real magic is that switching between them is instant. Mid-conversation, I can go from talking to a coding assistant to switching over to a creative writing persona without opening a new tab or starting a new session. It sounds small but it completely changed how I interact with local models.

And of course you can create your own. I have a custom one that’s basically “grumpy senior engineer who reviews my pull requests” and it’s genuinely caught bugs in my code. Would recommend.

Why “Uncensored”?

I get asked about the name. It’s not about being edgy. It’s about the principle that when you run AI locally, on your own hardware, you should have full control. No corporate content filters deciding what you can and can’t ask your own computer. That’s the whole point of local AI.

You pick the models. You set the boundaries. Your machine, your rules.

The Model Manager Saved My Sanity

Before this, I had a sticky note (a real physical one, stuck to my monitor) with a list of models I’d downloaded through Ollama. Which ones were good for code. Which ones were better for creative writing. Which ones were too big for my GPU.

The model manager in Locally Uncensored shows you everything you have installed, lets you pull new models, delete old ones, and see their sizes at a glance. It’s not revolutionary technology — it’s basically a nice UI over ollama list and ollama pull — but it’s one of those things where having it RIGHT THERE in the same app as your chat makes everything feel cohesive.

No more terminal tab just for model management. No more forgetting what you have installed.

What I Learned Building This

A few things that surprised me:

Ollama’s API is really well designed. Streaming responses, model management, it’s all clean and straightforward. Made my life way easier than I expected.

ComfyUI’s API is… not. I love ComfyUI and what it can do, but integrating with it programmatically was a journey. The workflow-based API is powerful but requires you to basically construct node graphs in JSON. I spent way too many late nights debugging why my image gen requests were silently failing. If you’re thinking about building on top of ComfyUI, budget extra time for this.

People really want local AI to be easier. I posted an early version in a few Discord servers expecting maybe 10 people to try it. The response was way beyond what I anticipated. Turns out a LOT of people are in the same boat — they want to run AI locally but the tooling fragmentation is a real barrier.

It’s Open Source, Go Break It

The whole thing is on GitHub: https://github.com/PurpleDoubleD/locally-uncensored

MIT licensed. Clone it, fork it, rip it apart, rebuild it, I don’t care. If you find a bug, open an issue. If you want to add a feature, PRs are welcome.

You’ll need Ollama installed for chat and a ComfyUI instance running for image/video gen. The README walks through setup — it’s pretty straightforward if you’ve already got those tools running.

What’s Next

I’m actively working on this. Some things on my radar:

  • Better ComfyUI workflow templates so you don’t need to configure anything for basic image gen
  • RAG support for chatting with your own documents
  • More personas (always more personas)
  • Possibly an Electron wrapper for a true desktop app experience

But honestly, the roadmap is shaped by whoever shows up and uses it. If you try it and think “man, I wish it did X” — tell me. Open an issue. That’s how the best features have been added so far.

TL;DR

I got tired of running five different tools to use local AI. So I built one app that does chat, image gen, and video gen in a single UI. It’s called Locally Uncensored, it runs 100% offline, and it’s open source.

Give it a spin if you’re drowning in AI tool tabs like I was. And if you hate it, at least you’ll have a clean starting point to build something better.

🔗 GitHub — Locally Uncensored

Stop Writing Tests First — Write Formal Specs. Let AI Agent Teams Build Your System.

The Problem Nobody’s Talking About

Everyone’s excited about AI-powered coding. GitHub Copilot autocompletes your functions. Claude and GPT-4 generate entire modules from prompts. But here’s the uncomfortable truth: we’re still thinking about AI as a single tool, when the real paradigm shift is multiple AI agents working as a team.

And that raises a question nobody’s answering well:

When three AI agents are building three modules in parallel, what prevents them from producing code that doesn’t fit together?

Natural language specs? Too ambiguous. One agent interprets “the order must be valid” differently from another. Test-driven development? Tests are inductive — they verify specific cases, not the contract between modules. You can have 100% coverage on each module and still watch the system crash at integration.

What If Agents Had Blueprints?

Think about how a construction project works. You don’t hand three subcontractors a paragraph of prose and hope for the best. You give them engineering drawings — precise, unambiguous documents that define every interface: where the plumbing connects to the electrical, what load the foundation bears, which walls are structural.

Formal specifications are the engineering drawings of software. And a method called VDM-SL (Vienna Development Method – Specification Language), originally from IBM in the 1970s, turns out to be nearly ideal for AI agents to read, write, and reason about.

Here’s why it matters for multi-agent development:

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

A’s postcondition implies B’s precondition. This is mechanically verifiable. No ambiguity. No “alignment meetings” between agents. No integration surprises.

A Concrete Example

Here’s what a VDM-SL specification for an order module actually looks like:

module Order

types
  OrderId = nat1;
  ProductId = nat1;

  LineItem :: productId : ProductId
             quantity : nat1
             unitPrice : nat;

  OrderStatus = <PENDING> | <CONFIRMED> | <COMPLETED> | <CANCELLED>;

  Order :: orderId    : OrderId
           customerId : nat1
           lineItems  : seq of LineItem
           status     : OrderStatus
           totalAmount : nat;

state OrderStore of
  orders    : map OrderId to Order
  nextOrderId : nat1
inv mk_OrderStore(orders, nextOrderId) ==
  nextOrderId > 0
  and forall id in set dom orders & orders(id).orderId = id

operations

CreateOrder: nat1 * seq of LineItem ==> OrderId
CreateOrder(customerId, items) ==
  let id = nextOrderId in (
    orders := orders ++ {id |-> mk_Order(id, customerId, items,
                          <PENDING>, SumItems(items))};
    nextOrderId := nextOrderId + 1;
    return id
  )
pre items <> []
post RESULT = nextOrderId~ - 1
     and RESULT in set dom orders
     and orders(RESULT).status = <PENDING>;

Notice what this gives you:

  • Types with invariants — nat1 means positive integers only. No null, no zero.
  • State invariant — the inv clause is always true. Every order ID in the map matches the order’s own ID.
  • Pre-conditions — items <> [] means you can’t create an empty order. This is a structural guarantee, not a test case.
  • Post-conditions — after CreateOrder, the order exists in the store with status PENDING. Any agent reading this knows exactly what to expect.

An AI agent building the inventory module doesn’t need to see the Order module’s implementation. It only needs the interface contract — the types and operation signatures with pre/post conditions. That’s a fraction of the context window.

The Multi-Agent Architecture

Here’s the workflow we propose:

Phase 1: Human + Architect AI (1-3 days)

The human (domain expert) and an architect AI collaborate to define:

  • System-wide VDM-SL specification
  • Module decomposition
  • Interface contracts between modules (the A.post ⇒ B.pre relationships)

The human doesn’t need to read VDM-SL. The AI explains the spec in natural language: “This says a customer can’t place an order unless they have at least one item, and after the order is placed, inventory is reserved. Does that match your business rules?”

Phase 2-4: Parallel Module Agents

Each module gets its own AI agent. Agent A builds Order. Agent B builds Inventory. Agent C builds Payment. In parallel.

Each agent’s context contains only:

  • Its own module spec (~200-500 lines of VDM-SL)
  • Interface contracts of dependent modules (~50-100 lines each)
  • Cross-cutting decisions from Phase 1

No agent needs another agent’s implementation code. This fits comfortably in current context windows.

Integration Verification

A dedicated integration agent checks A.post ⇒ B.pre across all module boundaries. It never reads implementation code — only interface specs. If Order’s ConfirmOrder postcondition guarantees stock_reserved = true, and Inventory’s ShipOrder precondition requires stock_reserved = true, the composition is verified.

┌──────────────────────────────────────────────────┐
│            Phase 1: Architecture                  │
│     Human + Architect AI                          │
│     → System spec + module decomposition          │
│     → Interface contracts (A.post ⇒ B.pre)        │
└────────────┬─────────────┬───────────────┬───────┘
             │             │               │
             ▼             ▼               ▼
     ┌──────────┐  ┌──────────┐   ┌──────────┐
     │ Agent A  │  │ Agent B  │   │ Agent C  │
     │ Order    │  │Inventory │   │ Payment  │
     │ Module   │  │ Module   │   │ Module   │
     └─────┬────┘  └────┬─────┘   └────┬─────┘
           │             │               │
           ▼             ▼               ▼
     ┌──────────────────────────────────────────┐
     │      Integration Verification Agent       │
     │   Checks A.post ⇒ B.pre across all       │
     │   module boundaries (spec-level only)     │
     └──────────────────────────────────────────┘

Why Not Just Use Natural Language Specs?

Three reasons:

1. Ambiguity scales exponentially. With 2 modules and natural language specs, you have a manageable amount of misinterpretation risk. With 10 modules and 45 pairwise interactions, the ambiguity explodes. Formal specs eliminate this at the root — nat1 means one thing, everywhere, always.

2. Verification becomes mechanical. You can’t machine-check whether “the order should be valid” in one module agrees with “valid orders have at least one item” in another. But you can machine-check whether Order.ConfirmOrder.post ⇒ Inventory.ReserveStock.pre.

3. Context windows stay small. Each agent needs only its module spec + dependency interfaces — not the full natural-language requirements document that keeps growing. A 10-module system might have 200 pages of natural language specs but only 50 lines of interface contract per module boundary.

Why TDD Doesn’t Solve This

This is the part that might get me some angry comments, but hear me out.

Dijkstra said it in 1969:

“Program testing can be used to show the presence of bugs, but never to show their absence.”

TDD is inductive reasoning: you verify specific inputs and hope they generalize. Formal specification is deductive reasoning: you state what must always be true, then derive implementations that satisfy those conditions.

For a single developer, TDD is a useful discipline. For multi-agent AI development, it fails structurally:

Aspect TDD Formal Specs
Reasoning Inductive (specific → general) Deductive (general → specific)
Inter-agent contract Implicit in shared tests Explicit in interface specs
Verification Run tests, hope for the best Mechanical proof of composition
Context per agent Needs test suite + code + shared state Needs only interface contracts
Scales with agents Coordination overhead grows Contracts remain verifiable

Note: we’re not saying “never write tests.” Tests still have a role — integration tests for external systems, performance tests, E2E UI tests. What we’re saying is: tests are no longer the center. Formal specifications are.

The Human Role: Not Obsolete, Elevated

This paradigm doesn’t remove humans. It redefines what humans do:

Before: Write code, write tests, review code, debug integration failures.

After:

  • Domain expert — “This is how our business works. These are the rules.”
  • Architecture decision maker — “Split the system into these modules. This module depends on that one.”
  • Quality judge — “The AI says the spec means X. Does X match what we actually need?”

You don’t need to read VDM-SL. You don’t need to write code. You need deep domain knowledge and the ability to ask sharp questions when the AI explains its specifications back to you.

The Open-Source Framework

We’ve packaged this entire methodology as an open-source project:

formal-spec-driven-dev — Apache 2.0 licensed

What’s included:

📄 Foundational paper (Japanese + English) — the full theoretical argument, ~30 min read

📋 VDM-SL templates — module template, interface contract template. Copy, customize, go.

🤖 AI prompt templates for all 4 phases:

  • Phase 1: Specification dialogue (elicit requirements → VDM-SL)
  • Phase 2: Technical design (VDM-SL → architecture decisions)
  • Phase 3: Implementation (VDM-SL → production code)
  • Phase 4: Verification (code ↔ spec cross-check)

🔧 Multi-agent orchestration:

  • agent-config.yaml — role definitions, context requirements, outputs
  • workflow.md — step-by-step guide to running multi-agent development today

📦 Working example: E-commerce order system with 3 modules (Order, Inventory, Payment), complete with VDM-SL specs, integration verification, and cross-module contracts.

Try It in 30 Minutes

  1. Clone the repo
  2. Open templates/prompts/phase1-specification.md
  3. Copy the system prompt into Claude or GPT-4
  4. Start describing a module from your own project
  5. Watch the AI produce a VDM-SL spec, then explain it back to you in plain language

That’s it. No VDM-SL expertise required. The AI reads and writes the formal notation. You verify the meaning.

What We Need from the Community

This is early-stage. The methodology is sound, but the tooling ecosystem needs work:

  • Domain templates — Healthcare, fintech, logistics, IoT. Each domain has its own patterns. We need VDM-SL templates for common domain models.
  • Orchestration tooling — Better ways to coordinate multiple AI agents with shared specifications. Integration with existing agent frameworks.
  • Case studies — Real teams trying this on real projects. What worked? What broke? Where are the rough edges?
  • Verification automation — Tools that automatically check A.post ⇒ B.pre across module boundaries.

If any of this resonates, check out the repo and open an issue. PRs welcome. Case study reports are especially valuable — even “I tried this and it didn’t work because…” helps enormously.

The Bottom Line

The era of single-agent AI coding is already giving way to multi-agent development. The unsolved problem is coordination. Natural language specs create ambiguity that scales exponentially with the number of agents. Tests verify specific cases but can’t guarantee compositional correctness.

Formal specifications — specifically, VDM-SL with pre/post conditions and invariants — provide what multi-agent development needs: unambiguous, mechanically verifiable contracts between agents.

The human role doesn’t disappear. It elevates. You become the domain expert and architect who ensures the AI team builds the right thing. The specs ensure they build it correctly.

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

This article is also available in Japanese (日本語).