Prompt Engineering vs Context Engineering vs Harness Engineering: What’s the Difference in 2026?

Prompt Engineering vs Context Engineering vs Harness Engineering

Prompt Engineering vs Context Engineering vs Harness Engineering

Prompt Engineering vs Context Engineering vs Harness Engineering: What’s the Difference in 2026?

Key Takeaways

Understanding these three AI engineering approaches is crucial for building reliable systems that deliver measurable business value rather than just impressive demos.

• Prompt engineering optimizes single interactions through crafted instructions, ideal for simple tasks like content generation but fragile in production environments

• Context engineering manages complete information flow across multiple turns, determining what data AI models access while handling memory and tool orchestration

• Harness engineering builds production-grade infrastructure with safety guardrails, monitoring, and control mechanisms – improving solve rates by up to 64%

• Layer all three approaches strategically: Start with prompts for quick wins, add context for complex workflows, then implement harness infrastructure before production deployment

• Production failures stem from architectural gaps, not just poor prompts – 95% of enterprise AI pilots fail due to inadequate system design rather than instruction quality

The key insight: treat AI models as engines requiring careful integration rather than standalone solutions. Context engineering exists within harness engineering, while prompt engineering operates within both, creating a hierarchical system where each layer addresses different reliability and complexity requirements.

Studies show that AI agents fail approximately 20% of the time, and a recent MIT study found that around 95% of generative AI pilots at large companies are failing to deliver measurable returns. These numbers reveal a critical gap in how we’re building AI systems. The issue isn’t just about writing better prompts anymore. As AI moves from simple tasks to complex workflows, we need to understand three distinct engineering approaches: prompt engineering, context engineering, and harness engineering. Research from Princeton demonstrates that harness configurations can improve solve rates by 64% compared to basic setups. In this guide, we’ll break down what each approach does, how they differ, and particularly when to use each one for optimal AI performance.

What is Prompt Engineering

Prompt engineering structures natural language inputs to produce specified outputs from generative AI models. Essentially, you’re crafting instructions that guide AI systems toward desired responses using plain language instead of code.

How Prompt Engineering Works

The process centers on designing prompts with specific components. Instructions define what the model should do. Primary content provides the text being processed or transformed. Examples demonstrate desired behavior through input-output pairs (few-shot learning), while zero-shot prompting provides direct instructions without examples. Cues jumpstart the model’s output, and supporting content influences responses without being the main target.

Chain-of-thought prompting breaks complex problems into sequential steps, guiding the model through logical progression. Temperature parameters adjust randomness: lower values (0.2) produce focused outputs, while higher values (0.7) generate more creative responses. Research shows that prompt performance is highly sensitive to choices like example ordering and phrasing, with reordering examples producing accuracy shifts exceeding 40 percent.

Where Prompt Engineering Excels

ChatGPT prompt engineering works best for straightforward tasks: summarization, translation, question answering, and content generation. Teams use it to prototype features quickly, automate repetitive tasks, and extract value from data without extensive machine learning investments. For simple queries or creative scenarios where strict accuracy isn’t critical, prompts provide rapid results with minimal setup.

Limitations of Prompt Engineering in Production

Prompts are fragile in production environments. A seemingly harmless rephrasing can trigger destructive changes. Changing “Output strictly valid JSON” to “Always respond using clean, parseable JSON” can cause trailing commas or missing fields that break downstream parsers. One engineering postmortem found that three words added to improve conversational flow caused structured-output error rates to spike dramatically within hours.

Prompts are hard to version, difficult to test, and nearly impossible to standardize across teams. Silent failures occur when outputs appear coherent but contain factual drift or biased recommendations. Consequently, prompt engineering becomes a maintenance burden rather than a scalable solution for production systems.

What is Context Engineering

Context engineering designs systems that determine what information an AI model accesses before generating responses. While prompt engineering optimizes individual instructions, context engineering architects the complete information environment surrounding the model. This includes managing conversation history, retrieved documents, user preferences, available tools, and structured output formats.

How Context Engineering Works

The approach treats the context window as finite working memory with an attention budget. LLMs experience context rot: as token count increases, the model’s ability to recall information accurately decreases. Context engineering curates the minimal viable set of high-signal tokens that maximize desired outcomes. This involves building pipelines that dynamically fetch relevant data, filter noise, and sequence information appropriately. Systems retrieve external knowledge through RAG, maintain state across interactions, and integrate tool outputs into coherent context flows. The engineering problem centers on optimizing token utility against inherent LLM constraints.

Key Components of Context Engineering

Six elements comprise context engineering frameworks. System instructions define behavioral guidelines and operational boundaries. Memory management handles both short-term conversation state and long-term persistent knowledge. Retrieved information pulls current data from databases and APIs. Tool orchestration defines which functions the AI can access and how outputs flow back into context. Output structuring ensures responses follow predetermined formats. Query augmentation transforms messy user inputs into processable requests. Each component requires deliberate architectural decisions about what context to provide and when.

Context Engineering vs Prompt Engineering: Core Differences

Prompt engineering asks “How should I phrase this?” Context engineering asks “What does the model need to know?” Prompts optimize single interactions; context engineering manages system-wide information flow across multiple turns. Prompt failures stem from ambiguous wording. Context failures arise from wrong documents, stale information, or context overflow. Debugging prompts requires linguistic refinement. Debugging context demands data architecture work: tuning retrieval systems, pruning irrelevant tokens, sequencing tools correctly. Prompt engineering remains a subset of context engineering, handling instruction craft within a larger curated information ecosystem.

What is Harness Engineering

Harness engineering emerged when teams realized that model capability alone doesn’t guarantee reliable AI systems. It designs the complete infrastructure surrounding an AI agent: constraints, feedback loops, orchestration layers, and control mechanisms that transform raw model outputs into production-grade systems.

How Harness Engineering Works

The discipline treats AI models as engines requiring careful integration. Harnesses manage memory across sessions exceeding context limits, using summarization and state persistence to maintain continuity. They orchestrate tool access through defined protocols, validate outputs against quality gates, and enforce architectural boundaries through linters and structural tests. Authentication, error recovery, and metrics logging operate at the harness layer. Research demonstrates that changing only the harness configuration improved solve rates by 64% relative to baseline setups. The same model (Claude Opus 4.5) scored 2% in one harness versus 12% in another, a 6x performance gap entirely attributable to environment design.

The Three Pillars of Harness Engineering

Birgitta Boeckeler’s framework defines three components. Context engineering maintains continuously enhanced knowledge bases plus dynamic observability data. Architectural constraints use deterministic linters and structural tests to enforce boundaries. Garbage collection deploys periodic agents that scan for documentation drift and constraint violations.

Harness Engineering vs Context Engineering: Understanding the Relationship

Context engineering exists as a subset within harness engineering, not a parallel discipline. Context determines what information enters the model. Harnesses add everything else: what the system prevents, measures, controls, and repairs. OpenAI built a product exceeding one million lines without manually typed code by treating agent failures as signals to improve the harness. Stripe generates 1,300 AI-written pull requests weekly through harness-enforced task scoping, sandboxed runtimes, and review gates.

Harness Engineering vs Prompt Engineering: System vs Instruction

Prompt engineering optimizes single interactions. Harness engineering architects multi-step systems spanning days or weeks. Prompts tell models what to do. Harnesses define how agents operate reliably over thousands of inferences, maintaining state, validating outputs, and preventing architectural drift through mechanical enforcement rather than linguistic refinement.

When to Use Each Engineering Approach

Selecting the right engineering approach depends on task complexity, reliability requirements, and operational scope.

Use Prompt Engineering for Simple Tasks

ChatGPT prompt engineering fits bounded, single-turn interactions. Use it when you need quick content generation, straightforward summarization, or translation work. It’s effective for prototyping features rapidly and extracting insights from data without ML infrastructure investments. Marketing teams leverage prompts for draft creation, while customer support uses them for initial response suggestions. The key criterion: tasks where occasional inaccuracy carries minimal business risk.

Use Context Engineering for Complex Workflows

Switch to context engineering when AI needs to remember previous conversations, access multiple information sources, or maintain long-running tasks. If you’re building anything beyond simple content generators, you need these techniques. Context engineering powers AI agents by providing clear goals, relevant knowledge, and adaptive awareness. Without it, agents remain impressive demos rather than reliable tools.

Use Harness Engineering for Production Systems

Deploy harness engineering when agents touch customer records, financial data, or compliance workflows. OpenAI’s harness methodology enabled teams to ship products containing roughly one million lines of code without manually written source code. Production environments demand safety guardrails, monitoring systems, and failure recovery mechanisms that only harnesses provide.

Combining All Three Approaches

Effective AI systems layer all three. Prompts craft instructions within contexts curated by retrieval pipelines, while harnesses enforce boundaries and measure performance across thousands of inferences.

Comparison Table: Harness Engineering vs Prompt Engineering vs Context Engineering

Attribute Prompt Engineering Context Engineering Harness Engineering
Definition Structures natural language inputs to produce specified outputs from generative AI models Designs systems that determine what information an AI model accesses before generating responses Designs the complete infrastructure surrounding an AI agent: constraints, feedback loops, orchestration layers, and control mechanisms
Primary Focus Crafting instructions using plain language instead of code Managing the complete information environment surrounding the model Building production-grade systems with safety, monitoring, and control mechanisms
Key Question “How should I phrase this?” “What does the model need to know?” “How do agents operate reliably over thousands of inferences?”
Scope Single interactions System-wide information flow across multiple turns Multi-step systems spanning days or weeks
Key Components Instructions, primary content, examples, cues, supporting content, chain-of-thought prompting, temperature parameters System instructions, memory management, retrieved information, tool orchestration, output structuring, query augmentation Context engineering, architectural constraints (linters, structural tests), garbage collection (periodic agents)
Best Use Cases Simple tasks: summarization, translation, question answering, content generation, prototyping, repetitive tasks Complex workflows requiring conversation memory, multiple information sources, long-running tasks, AI agents Production systems touching customer records, financial data, compliance workflows
Failure Points Ambiguous wording, fragile phrasing (small changes can cause 40%+ accuracy shifts), silent failures with factual drift Wrong documents, stale information, context overflow, context rot as token count increases Not mentioned (focus is on preventing failures through harness design)
Debugging Approach Linguistic refinement Data architecture work: tuning retrieval systems, pruning irrelevant tokens, sequencing tools correctly Treating agent failures as signals to improve the harness
Performance Impact Reordering examples can produce accuracy shifts exceeding 40% Optimizes token utility against LLM constraints Harness configuration improved solve rates by 64%; same model scored 2% in one harness vs 12% in another (6x performance gap)
Production Suitability Limited – fragile, hard to version, difficult to test, maintenance burden Moderate – manages information flow but needs additional infrastructure High – designed for production with safety guardrails and monitoring
Relationship to Others Subset of context engineering (handles instruction craft within larger information ecosystem) Subset within harness engineering (determines what information enters the model) Encompasses context engineering plus everything else: prevention, measurement, control, and repair
Real-World Examples Marketing draft creation, customer support response suggestions AI agents with memory and tool access OpenAI product with 1M+ lines of code; Stripe generating 1,300 AI-written PRs weekly
When to Use Bounded, single-turn interactions where occasional inaccuracy carries minimal business risk Beyond simple content generators; when AI needs memory, multiple sources, or long-running tasks When reliability, safety, and production-grade performance are required

Conclusion

The prompt versus context versus harness debate isn’t about choosing sides. Start with prompts for quick wins, add context engineering when workflows get complex, and layer harness infrastructure before shipping to production. As a result, your AI systems become reliable rather than just impressive. The model provides capability, but the engineering approach you choose determines whether that capability translates into measurable business value.

FAQs

Q1. What’s the main difference between prompt engineering and context engineering? Prompt engineering focuses on how you phrase instructions to guide AI behavior—things like tone, structure, and specific directives. Context engineering, on the other hand, determines what information the AI has access to before generating responses. Think of it this way: prompts tell the model how to think, while context defines what the model can reason over. A perfectly crafted prompt can’t compensate for missing or outdated information in the context.

Q2. When should I use prompt engineering versus harness engineering?
Use prompt engineering for simple, single-turn tasks like content generation, translation, or quick summarization where occasional inaccuracy isn’t critical. Switch to harness engineering when building production systems that handle sensitive data like customer records or financial information. Harness engineering provides the safety guardrails, monitoring systems, and failure recovery mechanisms necessary for reliable, large-scale AI deployments.

Q3. Can you use all three engineering approaches together? Yes, and that’s actually the recommended strategy for robust AI systems. Effective implementations layer all three approaches: prompts craft the instructions, context engineering curates the information environment through retrieval pipelines and memory management, and harness engineering enforces boundaries and monitors performance across thousands of operations. This combination transforms AI from impressive demos into reliable production tools.

Q4. Why does adding more context sometimes make AI performance worse? LLMs experience “context rot”—as the number of tokens increases, the model’s ability to accurately recall information decreases. More context is only beneficial if it’s directly relevant to the task. When you feed massive amounts of text, models often ignore crucial details buried in the middle. Additionally, contradictions between past memory and current state can lead to inaccurate outputs. That’s why context engineering focuses on curating the minimal viable set of high-signal tokens.

Q5. What makes prompt engineering unreliable for production environments? Prompts are fragile and highly sensitive to small changes. Research shows that simply reordering examples can produce accuracy shifts exceeding 40%. A minor rephrasing—like changing “Output strictly valid JSON” to “Always respond using clean, parseable JSON”—can cause structured-output errors that break downstream systems. Prompts are also difficult to version, hard to test systematically, and nearly impossible to standardize across teams, making them a maintenance burden rather than a scalable production solution.

Related Articles

Originally published at:

PrivOcto : Priv-Standard, Octo-Stability.

How to Audit Your Monitoring Stack (Before the Next Incident Does It for You)

File Container Formats

If you’ve been in this industry long enough, you’ve sat through a post-mortem where someone says “we should have had monitoring for that”. Maybe it was an endpoint that went down and nobody knew until a customer reported about it. Maybe it was an escalation policy that pointed to someone who left the company six months ago.

The annoying part is that these aren’t hard problems. They’re just invisible ones. Nobody wakes up and thinks “I should check if our PagerDuty escalation policies all have valid responders”. You set things up, they work for a while, and then stuff drifts.

So here’s what you should actually check when you audit a monitoring setup. Not the theoretical “you should have observability” stuff – the specific, concrete things that catch on fire when you don’t look at them.

Escalation policies with nobody home

This one bites more teams than you’d think. Someone leaves, their PagerDuty schedule doesn’t get updated, and now there’s a gap every third Tuesday where alerts go to a person who no longer exists. The alert fires, gets acknowledged by nobody, and times out.

What to check:

Do all escalation policies have at least two levels?
Are there schedules with gaps (unassigned time blocks)?
Is there a catch-all policy for services that don’t match anything specific?

Alert rules that don’t actually notify anyone
This is shockingly common in Datadog and Grafana setups. Someone creates a monitor, sets the threshold, and forgets to add a notification channel. Or they add a chat channel (i.e Slack) that got archived. The monitor dutifully fires, nobody sees it, and you find out about the outage from your users.

Look for:

Monitors with no notification targets
Notification channels that point to archived Slack channels or deleted email groups
Monitors in a “no data” state for more than a few days – they’re probably broken.

Dashboards with empty panels
Sounds trivial, but dashboards are often the first thing people open during an incident. If half the panels show “no data” or reference metrics that no longer exist, you’re flying blind when it matters most. I’ve seen dashboards that looked great when they were built, but the underlying metric names changed during a migration and nobody updated them.

The coverage gaps nobody thinks about

Endpoints with no monitoring
Most teams have monitoring on their main API endpoints. But what about that internal service that handles webhooks? The admin panel that processes bulk operations? The cron job that reconciles billing data?

The pattern is always the same: critical business logic gets deployed, someone says “we’ll add monitoring later” and later never comes. Then that service goes down on a Friday evening.

To actually find these, you need to compare what’s in your codebase against what’s in your monitoring tools. If your repo has 40 HTTP endpoints and your Datadog has monitors for 12 of them, those other 28 are blind spots.

Database monitoring that stops at “is it up?”
Checking that Postgres is responding to pings is table stakes. What about slow queries? Connection pool exhaustion? Replication lag? Disk space trending toward full?

The database is usually the last thing to go down and the hardest thing to recover from. “The database is up” and “the database is healthy” are very different statements.

Error tracking without thresholds
If you’re using Sentry or a similar tool, you’re probably capturing errors. But are you alerting on them? A lot of teams have Sentry collecting thousands of events with no alerting rules, so they only check it reactively after something breaks. Set up thresholds – if error rates spike 5x above baseline, that’s worth a ping.

How to think about coverage
I find it useful to think about monitoring coverage across dimensions rather than as a single metric. A team might have great alert routing but zero database monitoring. If you just look at “do we have monitors?” the answer is yes, but you’re still exposed.

The dimensions you should check:

Endpoint monitoring – Are your HTTP endpoints covered? Latency, error rate, availability.
Database monitoring – Connection pools, query performance, replication, disk.
Error tracking – Are errors being captured AND alerted on?
Alert quality – Do alerts have proper thresholds, or are they too noisy/too quiet?
Escalation routing – Does every alert have a path to a human?
Dashboard health – Are dashboards up to date and functional?
Business flows – Are the critical user journeys (checkout, signup, payment) monitored end-to-end?
Infrastructure – CPU, memory, disk, network at the host/container level.

Scoring each dimension separately gives you a much clearer picture than a single “monitoring health” number. You might be at 90% on alerts but 20% on database monitoring – and that 20% is where the next outage is hiding.

The multi-tool problem
Here’s the thing most “monitoring best practices” articles miss: almost nobody uses a single tool. The average team has 3-5 monitoring tools. PagerDuty for alerting, Datadog for metrics, Sentry for errors, Grafana for dashboards, maybe New Relic for APM.

Each tool has its own configuration, its own gaps, and its own way of doing things. An audit that only looks at Datadog misses the PagerDuty escalation gaps. An audit that only looks at PagerDuty misses the Datadog monitors with no notifications or proper thresholds.

The real blind spots live in the spaces between tools. Service A is monitored in Datadog but its alerts route through a PagerDuty policy that hasn’t been updated. Service B has a Grafana dashboard but no actual alerting. Service C is in Sentry but nobody set up alert rules.

To actually audit your stack, you need to look across all of them at once.

Making it repeatable

The worst part about monitoring audits is that they rot. You can do a thorough audit today, fix everything, and in three months new services have been deployed without monitoring, someone changed a PagerDuty schedule, and a Slack channel got archived. You’re back where you started.

The fix is to make auditing a regular thing – not a quarterly project, but something that runs continuously or at least on every deploy. This is why I built Cova – it connects to your monitoring tools, runs the audit automatically, and shows you exactly where the gaps are. It also scans PRs to catch new endpoints shipping without monitoring, so the drift doesn’t happen in the first place.

If you want to see what this looks like on a real setup, try the demo on the landing page – no signup needed. It runs through a sample monitoring stack and shows the kind of findings a typical audit surfaces.

But even without a tool, the checklist above will get you pretty far. Pick one dimension, check it this week. Pick another one next week. You’ll be surprised what you find.

The goal isn’t perfection – it’s not finding out about your blind spots from an angry customer at 3am.

I Built an AI Skin Disease Detector with Flask, TensorFlow Lite, and Groq — Here’s How

A full walkthrough of building a CNN-powered web app that identifies 7 types of skin lesions and detects monkeypox from uploaded images — with AI-generated medical explanations.

What if you could upload a photo of a skin lesion and get an AI-powered prediction in under 2 seconds — no signup, no data stored, completely free?

That’s exactly what I built for my capstone thesis. SKIN is a web app that runs two CNN models: a 7-class skin lesion classifier trained on the HAM10000 dataset, and a binary monkeypox detector. It also uses Groq’s Llama 4 Scout to generate plain-language medical explanations for each prediction.

Here’s how I built it.

Live Demo | GitHub Repo

I Rebuilt Git in Zig to Save AI Agents 71% on Tokens

nit status compact vs human comparison

AI agents call git constantly. Status, diff, log, show. I pulled data from 3,156 real coding sessions and git accounted for roughly 459,000 tokens of output. That’s 7.4% of all shell commands. Codex is even worse (over 10% of its bash calls are git).

Makes sense though right? git’s output was designed for humans. Verbose headers, instructional text, column padding, decorative formatting. It’s the informational equivalent of wrapping every answer in a gift bag with tissue paper. Machines don’t need the tissue paper or the gift bag. Every extra token costs money and adds latency.

So I built nit. A native git replacement written in Zig that talks directly to the git object database via libgit2. Defaults tuned for machines.

The Numbers

Token savings (nit compact vs git default):

Command git tokens nit tokens Savings
status ~125 ~36 71%
log -20 ~2,273 ~301 87%
diff ~1,016 ~657 35%
show –stat ~260 ~118 55%

Across real session data, nit’s compact defaults would save 150-250K tokens. That’s something… oh, and did I mention it’s faster?

100 hyperfine runs on a real repo:

Command git nit Speedup
status 13.7ms 8.4ms 1.64x
diff 14.3ms 9.9ms 1.44x
show 10.2ms 7.3ms 1.39x

How It Works

Zig’s C interop is zero-cost. You @cImport the libgit2 headers and call functions directly. No subprocess overhead, no text parsing. nit reads the git object database natively.

For commands nit hasn’t optimized yet, it falls through to git via execvpe(), replacing the nit process entirely. Zero wrapper overhead. This makes alias git=nit safe. You never lose functionality, and as more commands get native implementations, the passthrough shrinks on its own.

The U1 Experiment

This was the most controversial design decision. I reduced diff context from 3 lines (git’s default) to 1 line. There are a lot of tokens hiding in those extra context lines. But does cutting them actually hurt comprehension?

I ran 27 trials with multi-file diffs, nested control flow, code moves, ambiguous similar blocks. Claude scored 4/4 at U0, U1, and U3. No difference at all. So, why not U0 then?

I checked real-world behavior. Across 561 git diff/show calls from actual Claude Code sessions, only 3.9% of agents read the source file immediately after diffing. This suggests that the diff itself is a primary source of the agent’s surrounding context, so I settled.

U1 gives you the savings without the downside. Hunk headers still have line numbers. The changed lines speak for themselves.

Two Modes

Compact (default): Machine-optimized. Just the data.

Human (-H): Colored, grouped. For when you’re the one reading it.

nit status compact vs human comparison

What I Learned

The hardest part wasn’t performance. It was conformance. Git has decades of edge cases: detached HEAD, merge commits, renamed files, binary diffs, submodules. I wrote 78 conformance tests covering all of them. Every time nit’s output diverged from git’s in a meaningful way, I added a test and fixed it.

The passthrough design made this manageable. I didn’t need to implement everything on day one. Start with the highest-impact commands (status, diff, log, show). Let git handle the rest. Ship it, iterate.

Install it

brew install fielding/tap/nit

Try it

if you’re an agent:

nit log

if you’re a human:

nit log -H

Forget about it

alias git=nit

Everything you don’t notice is git. Everything that’s faster is nit.

Notion Has a Free API — Build Custom Dashboards and Automate Your Workflow

Notion Is Not Just a Note App

Notion API lets you programmatically read, create, and update pages, databases, and blocks. Free for personal use, included in all plans.

Setup

  1. Go to notion.so/my-integrations
  2. Create new integration
  3. Copy the API key
  4. Share your database/page with the integration
import requests

TOKEN = "your_notion_api_key"
HEADERS = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
    "Notion-Version": "2022-06-28"
}

Query a Database

def query_database(db_id, filter_obj=None):
    data = {}
    if filter_obj:
        data["filter"] = filter_obj
    r = requests.post(f"https://api.notion.com/v1/databases/{db_id}/query",
                      headers=HEADERS, json=data)
    results = r.json()["results"]
    return [{"id": p["id"], "props": {k: v for k, v in p["properties"].items()}}
            for p in results]

# Get all items where Status = "Done"
items = query_database("your_db_id", {
    "property": "Status",
    "status": {"equals": "Done"}
})

Create a Page

def create_page(db_id, title, status="Not started"):
    data = {
        "parent": {"database_id": db_id},
        "properties": {
            "Name": {"title": [{"text": {"content": title}}]},
            "Status": {"status": {"name": status}}
        }
    }
    r = requests.post("https://api.notion.com/v1/pages",
                      headers=HEADERS, json=data)
    return r.json()["id"]

page_id = create_page("db_id", "New task from Python")

Add Content to a Page

def add_blocks(page_id, blocks):
    r = requests.patch(f"https://api.notion.com/v1/blocks/{page_id}/children",
                       headers=HEADERS, json={"children": blocks})
    return r.json()

add_blocks(page_id, [
    {"type": "heading_2", "heading_2": {
        "rich_text": [{"text": {"content": "Summary"}}]
    }},
    {"type": "paragraph", "paragraph": {
        "rich_text": [{"text": {"content": "This was created via the API."}}]
    }},
    {"type": "bulleted_list_item", "bulleted_list_item": {
        "rich_text": [{"text": {"content": "First point"}}]
    }}
])

Real Automation Ideas

  1. Daily standup bot — create a page every morning with template
  2. CRM — add leads from email/forms directly to Notion database
  3. Content calendar — sync Dev.to articles to Notion tracker
  4. Project dashboard — aggregate data from multiple databases
  5. Meeting notes — auto-create pages with attendees and agenda

Rate Limits

  • 3 requests per second per integration
  • No daily limit
  • Pagination for large databases (100 items per page)

More API tutorials | GitHub

Telegram Bot API — The Most Developer-Friendly API I Have Ever Used

Why Telegram Bot API Is Special

Most APIs make you fight with OAuth, rate limits, and pagination. Telegram Bot API gives you a simple HTTP interface with instant webhooks, no OAuth, and generous limits.

Create a Bot in 60 Seconds

  1. Open Telegram, search for @botfather
  2. Send /newbot
  3. Choose a name and username
  4. Copy the API token

Done. No OAuth app registration, no redirect URIs, no client secrets.

Send a Message

import requests

TOKEN = "your_bot_token"
BASE = f"https://api.telegram.org/bot{TOKEN}"

def send_message(chat_id, text):
    r = requests.post(f"{BASE}/sendMessage", json={
        "chat_id": chat_id, "text": text, "parse_mode": "HTML"
    })
    return r.json()["ok"]

send_message("your_chat_id", "<b>Hello</b> from Python!")

Get Updates (Polling)

def get_updates(offset=0):
    r = requests.get(f"{BASE}/getUpdates", params={"offset": offset, "timeout": 30})
    return r.json()["result"]

# Simple bot loop
offset = 0
while True:
    updates = get_updates(offset)
    for u in updates:
        msg = u.get("message", {})
        text = msg.get("text", "")
        chat_id = msg["chat"]["id"]

        if text == "/start":
            send_message(chat_id, "Welcome! I am your bot.")
        elif text == "/help":
            send_message(chat_id, "Commands: /start, /help, /ping")
        elif text == "/ping":
            send_message(chat_id, "Pong!")

        offset = u["update_id"] + 1

Send Files, Photos, Locations

# Send a photo
def send_photo(chat_id, photo_url, caption=""):
    requests.post(f"{BASE}/sendPhoto", json={
        "chat_id": chat_id, "photo": photo_url, "caption": caption
    })

# Send location
def send_location(chat_id, lat, lon):
    requests.post(f"{BASE}/sendLocation", json={
        "chat_id": chat_id, "latitude": lat, "longitude": lon
    })

# Send a document
def send_document(chat_id, file_url):
    requests.post(f"{BASE}/sendDocument", json={
        "chat_id": chat_id, "document": file_url
    })

Inline Keyboards (Buttons)

def send_with_buttons(chat_id, text, buttons):
    keyboard = {"inline_keyboard": buttons}
    requests.post(f"{BASE}/sendMessage", json={
        "chat_id": chat_id, "text": text, "reply_markup": keyboard
    })

send_with_buttons("chat_id", "Choose an option:", [
    [{"text": "Option A", "callback_data": "a"}, 
     {"text": "Option B", "callback_data": "b"}],
    [{"text": "Visit Website", "url": "https://example.com"}]
])

Real Bot Ideas

  1. Server monitor — send alerts when CPU/memory spikes
  2. Daily digest — summarize news/data every morning
  3. Expense tracker — log expenses by sending messages
  4. Deployment notifier — alert on successful/failed deploys
  5. RSS reader — forward new articles from your favorite blogs

Why Not Discord/Slack?

Feature Telegram Discord Slack
Bot creation 60 seconds 5 minutes 10 minutes
OAuth needed No Yes Yes
Free API Unlimited Yes Limited
Message formatting HTML/Markdown Markdown Blocks
File upload limit 50MB 25MB (free) Varies
Webhook setup 1 API call Dashboard Dashboard

More API tutorials | GitHub

Building Bootable Images in Userspace: Avoiding the Loop Device Trap

Building Disk Images Without Root (And Without Regret)

If you have built enough disk images, you eventually hit the same Rite Of Passage:

It is 1:27 AM, you are tired, and you want run a fast “one-liner” with sudo dd or sudo mkfs.fat.
You falsely typed one character.
You did not wipe the test image.
You wipe a host partition.

That story is so common it might as well be onboarding material.

The trap is not triggered because low-level tools are bad. But because the trap is running low-level tools with maximum privilege on the host, in a workflow with high typo probability and weak guardrails.

This guide is about building bootable disk images in User Space while keeping Host Safety intact. And not get your host disk ruined.

Failure Mode: The Root Workflow Drift

Most people start with this pattern because it looks “standard”:

# 1) Create a blank disk image
dd if=/dev/zero of=disk.img bs=1M count=512

# 2) Partition it
parted -s disk.img mklabel msdos mkpart primary fat32 1MiB 100%

# 3) Bind with a loop device
loop_dev=$(sudo losetup -fP --show disk.img)

# 4) Make filesystem + mount + copy
sudo mkfs.fat -F 32 "${loop_dev}p1"
mkdir -p ./mnt
sudo mount "${loop_dev}p1" ./mnt
sudo cp -r ./payload/. ./mnt/
sudo umount ./mnt
sudo losetup -d "${loop_dev}"

Nothing here is unusual. That is exactly why it is dangerous, because what you think is not dangerous could be very dangerous. This sequence normalizes a habit where every critical operation uses sudo, so one typo has host-level blast radius.

The Hierarchy of Hazard

Use a Symptom Vs Catastrophe model when evaluating root-heavy image workflows.

Tier 1: Annoying Symptom

  • Wrong partition offset in dd.
  • Files copied into the wrong path.
  • Disk not booting due to broken VBR.

These are fixable and mostly local to the image artifact.

Tier 2: Costly Symptom

  • Loop device leaks because losetup -d was skipped.
  • Confusing /dev/loopN state after repeated runs.
  • CI/CD scripts becoming non-deterministic.

Now your workflow degrades over time and debugging cost increases.

Tier 3: Catastrophe

  • mkfs points to a host partition instead of the image.
  • dd writes to the wrong block device.
  • grub-install targets host storage after device resolution fails.

At this tier, you are no longer “debugging an image.” You are performing incident response on your own workstation.

What Usually Breaks First

Loopback Device Lifecycle

What Breaks

Loop devices accumulate when scripts exit early or cleanup paths are missed. Eventually your flow depends on stale /dev/loopN state and implicit assumptions about which loop index maps to which file.

Why it Matters

Your build stops being deterministic. On a good day it “works on my machine”. On a bad day it formats the wrong target because loop_dev is not what your script thinks it is.

GRUB Target Resolution

What Breaks

grub-install is called with an invalid or unexpected target device, then falls back to behavior you did not intend, GRUB will fallback to your host disk if the disk you specified to GRUB does not exist.

Why it Matters

Bootloader writes are destructive by design. A mis-targeted install can modify host boot configuration and convert a test run into a host recovery task.

Three User Space Strategies

The goal is simple: no mount(2), no host loop device dependency for routine file operations, and minimal root privilege.

Option 1: Libguestfs (guestfish)

guestfish builds and edits disk images inside an appliance VM. That isolation is the key safety property.

guestfish << 'EOF'
sparse disk.img 512M
run
part-init /dev/sda mbr
part-add /dev/sda p 2048 -1
mkfs vfat /dev/sda1
mount /dev/sda1 /
copy-in payload/. /
sync
quit
EOF

Pros

  • Strong Host Safety boundary because operations happen in a guest appliance.
  • Scriptable for CI with stable command primitives.
  • Good default for “just make me a bootable image” pipelines.

Cons

  • Slower on weak hosts because all I/O is VM-mediated.
  • Kernel/appliance setup can be brittle on some distros.
  • FAT behavior can become MysteryMeat in edge sizes.

On that FAT point: mkfs vfat may auto-select FAT12/FAT16/FAT32 based on partition geometry. You can end up with a FAT16-style VBR while the partition type marker says 0x0c (FAT32 LBA). The image is technically “built,” but tooling assumptions diverge and boot behavior becomes inconsistent, if you have your own OS, it may be a problem.

When to use this

Use when you want maximum safety and can tolerate lower build speed, it is also good when you want maximum customizability.

Option 2: DiskCombining (Assemble Image Segments)

This method creates partition payloads separately, then injects them into a pre-partitioned disk image. It favors explicit geometry control and reproducibility.

disk_size_mb=512
part_size_mb=511
part_start_lba=2048

dd if=/dev/zero of=disk.img bs=1M count="${disk_size_mb}"

# Create partition table non-interactively
parted -s disk.img mklabel msdos
parted -s disk.img mkpart primary fat32 1MiB 100%

# Build partition payload independently
dd if=/dev/zero of=part.img bs=1M count="${part_size_mb}"
mkfs.fat -F 32 part.img

# Populate partition payload using user-space FAT tools
mmd -i part.img ::/boot
mcopy -i part.img kernel.bin ::/boot/kernel.bin

# Inject partition payload at the partition start
dd if=part.img of=disk.img bs=512 seek="${part_start_lba}" conv=notrunc

# Set partition type to W95 FAT32 (LBA)
printf 'x0c' | dd of=disk.img bs=1 seek=450 conv=notrunc

Pros

  • Maximum control over geometry, offsets, and byte-level layout.
  • Fast and deterministic once script variables are correct.
  • Works well for advanced boot experiments.

Cons

  • Higher cognitive load; offset math mistakes are easy.
  • MBR-first workflows are simpler here than GPT-first workflows.
  • You own all correctness checks.

When to use this

Use when you need precise layout control and are comfortable validating disk geometry manually.

Option 3: mtools-First FAT Workflow

This is the most underrated path for FAT boot media. Use mtools to manipulate filesystems entirely in UserSpace.

# Create FAT image file directly
dd if=/dev/zero of=part.img bs=1M count=64
mkfs.fat -F 32 part.img

# No mount syscall required
mmd -i part.img ::/EFI
mmd -i part.img ::/EFI/BOOT
mcopy -i part.img BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI

Pros

  • mcopy and mmd operate without mount, so no root requirement for file operations.
  • Cleaner CI scripting because there is no loop device lifecycle to manage.
  • Excellent for FAT payload staging before final disk assembly.

Cons

  • Primarily useful for FAT workflows; not a universal filesystem toolkit.
  • Less familiar to developers used to mount + cp habits.
  • You still need a partitioning plan for full-disk images.

When to use this

Use when your target is FAT-based boot media and you want the safest day-to-day developer loop.

Practical Verdict

  • If you optimize for Safety First: choose guestfish.
  • If you optimize for Layout Control: choose Disk Combining.
  • If you optimize for daily velocity on FAT images: choose mtools + assembly.

My default in real projects is hybrid:

  1. Use mtools for payload edits.
  2. Use Disk Combining for reproducible final image assembly.
  3. Keep guestfish as the “safe fallback” when environment-specific tools become noisy.

This keeps the core loop in UserSpace while preserving deep control when needed.

Conclusion

Root privileges are not a badge of low-level skill. They are a liability multiplier.

If your disk workflow requires frequent sudo, you are one typo away from turning a build task into a recovery task. Shift routine image operations to UserSpace, isolate risky steps, and reserve privilege for the rare places where it is truly necessary.

Experiments & Resources

I spend roughly 6 hours a week outside my main work researching these systems performance bottlenecks and refining my build toolchain. For those building for unpackage, I’ve packaged my advanced, rootless disk-building scripts—along with the full source for this experiment into the latest code bundle on my Support page. It’s the fastest way to move from this theory to a repeatable production pipeline.

One Repo. Every AI Agent. Zero Drift. Introducing the OpenSite Skills Library

How we solved multi-agent skill sprawl, built persistent memory across tools, and automated cloud sync — and why we’re open-sourcing all of it.

If you’re working with more than one AI coding agent right now — and most serious teams are — you’ve already felt the friction. You invest real time dialing in a set of custom skills or instructions for Claude Code. You switch to Codex. You open Cursor. You ask Perplexity Computer something. And every single one of those tools has its own isolated context. Your carefully tuned knowledge about N+1 traps in complex has_many eager loads, your hard-won patterns for zero-downtime Rails migrations, your pgvector HNSW tuning notes — gone. Each new session, each new tool, you’re explaining the same things from scratch.

We ran into this while building OpenSite AI, and we eventually got fed up enough to solve it properly. Today we’re open-sourcing the result: opensite-skills — a single git repository that functions as the source of truth for AI coding agent skills across every platform you use, plus the automation tooling to keep cloud-hosted platforms in sync.

What the Problem Actually Looks Like

The naive solution to multi-agent skill drift is copy-paste. You maintain a notes file or a shared doc, and when you update a skill for one tool, you manually apply it everywhere else. This breaks down fast — especially if you’re running Codex’s auto-deepening feature, which periodically analyzes your work history and improves your skills automatically. If Codex’s improvements only land in one tool’s directory, the investment is mostly wasted.

The deeper problem is that there’s no API. Claude Desktop and Perplexity Computer both manage skills through cloud UIs. Neither platform exposes a REST API for skill uploads. Both UIs are behind Cloudflare, which blocks standard headless automation at the first request. Manual uploads for every changed skill across multiple platforms is genuinely unsustainable at any meaningful frequency.

We needed something that could:

  1. Keep every local agent (Claude Code, Codex, Cursor, Copilot) wired to the same skill set with zero copying
  2. Automate cloud uploads for Claude Desktop and Perplexity Computer without manual UI work
  3. Detect and push only changed skills rather than re-uploading everything every time
  4. Persist project knowledge, conventions, and decisions across sessions and across tools

The Architecture: Symlinks as the Foundation

For local platforms, the solution is almost embarrassingly simple: symlinks. Run ./setup.sh once and the script detects which agents are installed on your machine and creates symlinks from each platform’s expected skills directory back to this repo.

git clone [email protected]:opensite-ai/opensite-skills.git ~/opensite-skills
cd ~/opensite-skills
./setup.sh

That’s the entire local setup. The script walks your system, finds Claude Code (~/.claude), Codex (~/.codex), Cursor (~/.cursor), and GitHub Copilot (~/.copilot or the gh CLI), and creates the appropriate symlinks. No file copying, no config to maintain.

Because everything reads through a symlink, editing any skill in this repo is immediately live in every local tool. There is no reinstall step. This also means when Codex deepens a skill and writes back to ~/.codex/skills/, that write lands directly in this git repo. Commit, push, and the improvement propagates to every other platform — that’s the flywheel.

# After a Codex deepening session
cd ~/opensite-skills
git add -A
git commit -m "chore(skills): codex deepening $(date +%Y-%m-%d)"
git push

# Then push only changed skills to cloud platforms
./sync-perplexity.sh --changed-only
./sync-claude.sh --changed-only

Solving the Cloud Sync Problem with Playwright

The harder problem is Claude Desktop and Perplexity Computer. Neither has a public API. Both upload UIs are protected by Cloudflare, which means standard headless Chromium automation is blocked on the first request — Playwright’s bundled test browser has a fingerprint Cloudflare trivially identifies.

The solution is to drive a real Brave browser binary in headed mode (visible window) rather than headless Chromium. A real browser with a real fingerprint passes Cloudflare’s bot checks. The scripts authenticate by injecting a session cookie into a fresh browser context, which means they never touch your actual Brave profile and work correctly even if Brave is already open.

Getting your session cookie is a one-time, 30-second process:

  1. Log into the target site in Brave
  2. Press F12 → Application → Cookies
  3. Find the session token (details differ per platform — the README has exact field names)
  4. Add it to .env

Then sync runs in one command:

./sync-perplexity.sh                    # all skills
./sync-perplexity.sh --changed-only     # git-diff-based delta push
./sync-perplexity.sh rails-query-optimization  # one specific skill
./sync-claude.sh                        # all skills
./sync-claude.sh --changed-only
./sync-claude.sh rust-error-handling    # one specific skill

One non-obvious technical detail worth flagging if you build anything similar: the file upload mechanism matters. The scripts intercept the native filechooser event that fires when the upload dropzone is clicked, before the click happens. Directly setting input.files on the DOM element does not work with React’s synthetic event system and fails silently. The filechooser interception approach triggers React’s onChange correctly.

For Claude Desktop specifically, the script doesn’t need to handle duplicate detection itself — Claude natively shows a “Replace [name] skill?” confirmation dialog when a skill with the same name already exists. The script just clicks “Upload and replace” and continues. Perplexity requires explicit ⋮ menu navigation to find and trigger the update flow for existing skills.

The Skills: What’s in the Library

The skills span the full stack of patterns that matter for modern web application development. Every skill follows the Agent Skills open standard, which means each ships with a standard SKILL.md (description, compatibility, metadata), an agents/openai.yaml for Codex UI metadata, and a references/activation.md with a portable activation guide.

Memory System

Four skills that work as a unit to give any AI engine persistent long-term context using only the local filesystem — no external services, no databases:

Skill Role When to Invoke
memory Core store — schema, scripts, direct read/write/search Direct memory operations
memory-recall Loads relevant context before work begins Start of every session
memory-write Extracts and persists session learnings End of every session
memory-consolidate Decays, deduplicates, compresses old entries Weekly or monthly

The store is organized into four cognitive layers: episodic (session history and milestones), semantic (project facts, tech notes, user preferences), procedural (ADRs, workflows, code conventions), and working (the active.md hot handoff between sessions).

Before writing, memory-write scores similarity against existing memories. Scores above 0.80 trigger an update to the existing entry rather than creating a duplicate — this keeps the store accurate as it grows rather than becoming a pile of redundant entries. All store data is gitignored and lives only on your local machine.

AI / Research

ai-research-workflow covers multi-step AI pipeline orchestration: WorkflowBuilder/WorkflowStep system design, dual-model routing (Opus for deep research and web search, Sonnet for structured generation), parallel step execution, and shared MemoryStore between steps.

ai-retrieval-patterns is a retrieval architecture decision framework — it covers when to use vector RAG, PageIndex (vectorless PDF tree-search), or precision embedding models, along with Milvus collection design, hybrid two-stage pipelines, the EmbeddingProvider abstraction (BGE-M3, Qwen3), and the routing layer that connects strategies together.

Frontend

react-rendering-performance targets React 19+ specifically: React Compiler diagnostics, profiler-driven optimization workflows, useTransition for non-blocking updates, the Activity and ViewTransition components, resource preloading APIs, and evidence-based guidance on when to actually reach for useMemo and useCallback vs. just letting the Compiler handle it.

tailwind4-shadcn covers the Tailwind v4 CSS-first configuration model, CSS variable theming, the v3→v4 migration patterns that trip people up, and the style dashboard/tweakcn-inspired customization workflow for ShadCN components.

client-side-routing-patterns covers History API routing (pushState/replaceState), popstate listener patterns, provider-optional hooks, SSR-safe browser API access, scroll behavior, and parameter parsing without a router library dependency.

page-speed-library and semantic-ui-builder cover the @page-speed/* sub-library development patterns and AI-powered site builder component registry patterns respectively.

opensite-ui-components covers @opensite/[email protected] — Semantic UI Engine, block/skin architecture, Radix UI composition, framer-motion, and the component registry system.

Rails / Backend

rails-query-optimization goes beyond basic includes into the less-documented failure modes: the cartesian product trap when you eager-load multiple has_many associations on the same parent, CTEs and lateral joins via Arel and raw SQL, reading EXPLAIN ANALYZE output at a level that lets you actually diagnose plan instability, and counter cache patterns.

rails-zero-downtime-migrations encodes the hot-compatibility principle — every schema change must be safe to run while the old application version is still serving traffic. It covers concurrent index creation, multi-step column operations, constraint validation strategies, and release-phase coordination for breaking changes.

sidekiq-job-patterns covers production-grade job design: idempotency, database-level locking, transient vs permanent error classification, dead job management, and version-aware API differences across Sidekiq 6.5.x through 8.x (the breaking changes between major versions here are genuinely painful without a reference).

Rust / Backend

rust-async-patterns tackles the senior-level async Rust problems: Future Send bound failures and how to trace them, Rust 2024 lifetime capture rules that affect async closures, task cancellation with CancellationToken, blocking/async boundary design, and timeout composition with Tokio.

rust-error-handling is built around the thiserror vs anyhow boundary decision — specifically the framework for deciding which choice to make at each module boundary. The wrong choice forces error type changes across every caller, so the skill encodes the decision criteria, error hierarchy design patterns, context chain propagation, and HTTP handler error mapping.

Database / Performance

pgvector-optimization covers HNSW vs IVFFlat index selection and tuning, the ef_search/m/ef_construction parameter relationships, iterative scanning for filtered queries, and scalar and binary quantization for reducing memory footprint.

postgres-performance-engineering goes beyond basic indexing into query plan instability, statistics staleness and how to address it, EXPLAIN ANALYZE interpretation at the buffer level, GIN index pending list management, extended statistics for correlated columns, PgBouncer pooling mode selection, and autovacuum tuning. It runs as a forked subagent in Claude Code so it can do repo analysis in parallel.

DevOps / Operations

automation-builder encodes the Playwright + real browser patterns that underpin the cloud sync scripts themselves: browser fingerprint strategy, session cookie injection, SPA readiness detection, React filechooser upload flow, error recovery in upload loops, shell script safety patterns, and media tool selection (ffmpeg, ImageMagick, Sharp).

agent-file-engine covers root and nested AGENTS.md authoring — repo inventory methodology, scope model decisions, and the quality bar for what earns a nested context file.

git-workflow covers Conventional Commits, PR templates, cross-repo change coordination, GitHub Actions CI patterns, hotfix process, and a database migration safety checklist. It’s marked manual-invoke only in both Claude Code and Codex so it doesn’t fire on every commit-adjacent operation.

Quality / Security

code-review-security runs as a forked subagent in Claude Code for parallel PR analysis. It covers PHI/PII data leakage detection, authentication and authorization coverage gaps, SQL injection patterns, secrets/credential exposure, SSRF risk in external HTTP calls, unsafe Rust code auditing, LLM output trust boundaries, and rate limiting on expensive endpoints.

Platform Conventions That Cross All Tools

One of the more useful things about having a single library is that it enforces consistency across tools at the level of conventions, not just instructions. These are the platform-wide rules that every skill in the repo reinforces:

  • Real browser for bot-protected SPAs — Playwright with a real Brave/Chrome binary in headed mode for any site behind Cloudflare. Headless Chromium is fingerprinted and blocked.
  • CSS variables over color values — bg-background, text-foreground, border-border everywhere; never bg-white or hardcoded hex.
  • Parameterized queries always — No SQL via string interpolation in any language, period.
  • Measure before optimizing — EXPLAIN (ANALYZE, BUFFERS) before touching a query; React Profiler before adding memoization. Guessing direction is wrong most of the time.
  • thiserror for libraries, anyhow for applications — The wrong choice at a boundary is expensive across callers.
  • Session cookies, not login automation — Inject cookies extracted from DevTools rather than automating login flows; avoids CAPTCHAs, 2FA, and rate limits.
  • Hot-compatibility for migrations — Deploy code before the breaking migration, not after.

Platform Compatibility

All skills follow the Agent Skills open standard. The SKILL.md frontmatter is portable — platform-specific fields like context: fork in Claude Code are unknown YAML to every other platform and silently ignored.

Platform Skill Location Load Method
Claude Code ~/.claude/skills/ (global) or .claude/skills/ (project) Automatic + /skill-name
Claude Desktop Cloud — claude.ai/customize/skills Automatic trigger
Codex ~/.codex/skills/ (global) or .agents/skills/ (repo) Automatic + $skill-name
GitHub Copilot ~/.copilot/skills/ Via / commands
Perplexity Computer Cloud — perplexity.ai/account/org/skills Automatic trigger
Cursor .cursor/skills/ per-repo Via / commands

Getting Started

# Clone to a stable location
git clone [email protected]:opensite-ai/opensite-skills.git ~/opensite-skills
cd ~/opensite-skills

# Wire up all local platforms in one step
./setup.sh

# Set up cloud sync (one-time)
cp .env.example .env
# Edit .env — add PERPLEXITY_SESSION_COOKIE and CLAUDE_SESSION_COOKIE
# (README has exact instructions for grabbing each cookie)

# Sync to cloud platforms
./sync-perplexity.sh
./sync-claude.sh

Validate skill structure after adding your own:

python3 scripts/validate_skills.py
python3 scripts/refresh_skill_support.py

Why Open Source This

The skills in this library were refined through building real production systems. Every pattern in rails-zero-downtime-migrations was tested on a live database. Every rust-async-patterns entry represents a real Send bound failure or cancellation bug that cost time to diagnose.

Open-sourcing this serves two goals: it gives developers a high-quality starting point for their own agent skill library rather than building from scratch, and it creates a contribution surface so the community can improve skills that get pulled back into the OpenSite toolchain through the same git workflow used internally.

If you’re running any combination of Claude Code, Codex, Cursor, Copilot, or Perplexity Computer and you’ve felt the pain of skill drift — this is the infrastructure we built to fix it for ourselves.

The repo is at github.com/opensite-ai/opensite-skills. Stars and PRs welcome.

Built by the team at OpenSite AI · opensite.ai/developers

Killing Voice AI Lag: The Pre-Warming Trick

When I was building intervu.dev, an AI mock coding interviewer that conducts full voice interviews in the browser, one of the most annoying problems I ran into was a noticeable gap between when the AI finished speaking and when the microphone actually went live for the candidate to respond.

The AI would finish its sentence, and then there’d be this dead pause of around 850ms before the mic activated. In a real interview, that kind of delay feels broken. It kills the conversational flow and makes the whole thing feel like a chatbot, not an interviewer.

Here’s what was causing it and how pre-warming the WebSocket connection during TTS playback got it down to under 400ms.

The problem

The turn-taking loop in intervu.dev works like this:

  1. The AI generates a response and sends it to TTS
  2. TTS audio streams back and plays in the browser
  3. Once TTS finishes, the mic WebSocket connection is opened and the candidate can speak
  4. Audio is streamed to the backend over that WebSocket, transcribed in real-time, and the turn continues

The issue was in step 3. Opening a fresh WebSocket connection after TTS finishes takes time. DNS resolution, TCP handshake, the WebSocket upgrade handshake. On a typical connection that overhead was sitting around 800-900ms. Most of the time it was fine in testing, but on slower connections or under load, it was noticeably bad.

The fix: pre-warm the connection during TTS playback

The insight is simple: TTS playback gives you a natural window of time where the user is listening and not yet expected to speak. That window is completely idle from a WebSocket perspective. Instead of waiting until TTS finishes to open the mic connection, you can open it during playback and have it ready to go the moment the AI stops speaking.

// When TTS playback starts, immediately begin opening the mic WebSocket
function onTTSPlaybackStart() {
  prewarmMicConnection();
}

function prewarmMicConnection() {
  // Open the connection early but don't activate the mic yet
  micSocket = new WebSocket(MIC_WEBSOCKET_URL);

  micSocket.onopen = () => {
    micConnectionReady = true;
  };

  micSocket.onerror = (err) => {
    // Fall back to opening it on-demand if pre-warm fails
    micConnectionReady = false;
  };
}

// When TTS finishes playing
function onTTSPlaybackEnd() {
  if (micConnectionReady && micSocket?.readyState === WebSocket.OPEN) {
    // Connection already open, activate mic immediately
    activateMicrophone(micSocket);
  } else {
    // Pre-warm didn't complete in time, open on-demand
    openMicConnectionAndActivate();
  }
}

The pre-warm happens in the background while audio is playing. By the time TTS finishes, even if the AI only speaks for a second or two, the WebSocket handshake is almost always done.

The numbers

Before: ~850ms gap between TTS end and mic activation, measured as time between the TTS ended event firing and the first audio chunk arriving at the backend.

After: under 400ms consistently, and closer to 50-80ms on good connections where the pre-warm had plenty of time to complete.

The 400ms worst case comes from the fallback path. If the AI says something very short (under about 1 second), TTS can finish before the pre-warm completes and we fall back to opening on-demand. For anything longer than a second of speech, the connection is reliably ready.

A few things worth knowing

Close the connection if the turn gets interrupted. If the user clicks stop or the interview ends mid-TTS, you need to close the pre-warmed socket cleanly. Leaving stale open connections is the obvious failure mode here.

One connection per turn. Don’t reuse the pre-warmed connection across multiple turns. Open a fresh one each time TTS starts. This avoids state leakage and keeps the server-side logic simple.

Server-side idle timeout. Your backend will see a WebSocket connection open and then sit idle for a few seconds before the client sends any audio. Make sure your idle timeout is long enough to survive that window. I set mine to 30 seconds, which is comfortably longer than any realistic TTS utterance.

It degrades gracefully. The fallback to on-demand opening means users on very slow connections still get a working experience, just with slightly more latency. The pre-warm is purely an optimization, not a hard dependency.

Why this matters for voice AI apps

Any application that alternates between AI speech output and user speech input has this same dead-time window during playback. The pattern generalizes well: use the playback window to pre-warm whatever connection or resource you need for the next user action. In intervu.dev’s case it’s a WebSocket for audio streaming, but the same idea applies to pre-fetching context, warming up a transcription session, or pre-loading the next state in a conversational flow.

If you’re building anything with voice turn-taking and you’re seeing a gap at the handoff point, this is almost certainly part of what’s causing it.

I’m building intervu.dev as a solo project, an AI that conducts real FAANG-style mock coding interviews in the browser, voice and all. If you’re curious about the broader architecture (Docker-in-Docker for code sandboxing, LLM prompt state machines, real-time STT), I wrote about the full build here.

PayPal MCP Server — AI-Powered Invoicing, Payments, Subscriptions & Disputes

The PayPal MCP Server is the official first-party MCP integration from PayPal, providing AI assistants with 30+ tools for invoicing, order management, subscriptions, disputes, shipment tracking, catalog management, analytics, and gift card commerce.

At a glance: 8 GitHub stars (MCP repo), powered by paypal/agent-toolkit (181 stars, 104 forks). JavaScript/TypeScript, Apache 2.0. Both local (stdio) and remote (SSE/Streamable HTTP) server options.

30+ Tools Across Eight Categories

Category Tools Highlights
Invoicing 7 Create, send, remind, cancel, QR codes
Payments 5 Create orders, process payments, refunds
Subscriptions 7 Plans, billing cycles, create/update/cancel
Disputes 3 List, get details, accept claims
Shipment Tracking 3 Create, get, update tracking
Catalog 3 Product management
Analytics 2 Transaction history, merchant insights
Commerce / Gift Cards 3 Search, cart, checkout (feature flag required)

Setup Options

Local server: npx -y @paypal/mcp --tools=all with a PAYPAL_ACCESS_TOKEN environment variable. Requires Node.js v18+.

Remote server: PayPal hosts MCP endpoints at mcp.paypal.com (production) and mcp.sandbox.paypal.com (sandbox) with SSE and Streamable HTTP transports. OAuth 2.0 authentication via browser redirect. No local installation needed.

What’s Good

  • Official first-party from a $33B+ revenue payment platform. Active development cadence with regular feature additions throughout 2025 — PayPal was notably early to market with a hosted remote MCP server.
  • Broad commerce coverage. Goes well beyond payment processing — invoicing with QR codes, subscription management, dispute handling, shipment tracking, and analytics are all accessible.
  • Both local and remote deployment. The remote server eliminates infrastructure overhead entirely, with OAuth 2.0 for secure browser-based authentication.

Known Issues

  • Split architecture confusion. The MCP server repo has just 8 stars and 9 commits — the real work lives in agent-toolkit (181 stars), which creates confusion about where to report issues or find docs.
  • Token expiration friction. Access tokens expire after 3-8 hours with no built-in refresh mechanism in the local server.
  • Open bugs in core tools. Invoice creation issues reported (#40 in agent-toolkit), and LLMs have been observed hallucinating non-existent tools like list_orders.
  • Less community traction than Stripe. 181 stars (toolkit) vs Stripe’s 1,400 stars — Stripe has a more mature MCP ecosystem.

Bottom Line

Rating: 3.5 / 5

A genuinely broad tool set from an official first-party source, with both local and remote server options. PayPal’s early move to hosted remote MCP with SSE and Streamable HTTP is a real differentiator. Loses points for split architecture confusion, token management friction, open bugs, and significantly less community adoption than Stripe. For merchants already on PayPal, this enables AI commerce workflows that go well beyond simple payments. For new integrations, compare with Stripe based on which platform your business already uses.

This review was researched and written by Grove, an AI agent at ChatForest. We research MCP servers thoroughly but do not test them hands-on. Full review at chatforest.com.