#100DaysOfSolana Day29: My Experience Generating Token On Solana Devnet

Yesterday felt different.

I just created my first token on Solana Devnet using the SPL Token Program… and I can’t fully explain the excitement that came with seeing it live on-chain.

Not a fake database entry.
Not something sitting quietly in a backend server.

An actual token hosted on the blockchain that anyone can verify.

One thing that really stood out to me is how much Solana abstracts away the hard parts. In Web2, building something like this would mean creating database schemas, APIs, handling double-spending logic, server maintenance, and a lot more stress.

But here?
The SPL Token Program already provides a secure and audited foundation to build on.

I also started understanding the structure behind it all:
• The Mint account acts as the source of truth for the token
• The Token account stores individual balances
• And every wallet needs its own token account before receiving tokens

At first, it felt unusual.
Now, it’s starting to make sense.

This may look like a small step, but for me, it feels like the beginning of something much bigger.

Excited to keep building, keep breaking things, and keep learning deeper layers of Solana.

Overcoming Challenges and Applying Best Practices in Migrating Large JavaScript Codebases to TypeScript

Migrating a large JavaScript codebase to TypeScript is a significant endeavor that offers long-term benefits like improved maintainability, safer code, and better tooling. However, it also introduces various challenges. Here’s how to navigate the process efficiently:

Common Challenges

  • Scale and Complexity: Large codebases involve many modules, dependencies, and legacy patterns, increasing migration overhead.
  • Type Definition Gaps: External libraries or code without type definitions can hinder smooth adoption.
  • Team Familiarity: Developers might lack experience with TypeScript, leading to lower productivity initially.
  • Incremental Conversion Difficulty: Refactoring code incrementally without breaking existing functionality is complex.
  • Build and Tooling Issues: Updating build tools (webpack, Babel) and CI/CD pipelines to support TypeScript may require non-trivial changes.

Best Practices for a Successful Migration

  • Start Small, Migrate Incrementally: Begin by renaming files from .js to .ts or .tsx, enabling allowJs in tsconfig.json. Adopt a gradual approach with frequent, small pull requests.

Example:

  • Convert an isolated utility function first:

    // Before (JavaScript)
    function sum(a, b) { return a + b; }
    
```ts
// After (TypeScript)
function sum(a: number, b: number): number { return a + b; }
```
  • Leverage TypeScript’s Flexibility: Use any or unknown types during migration to unblock progress, but incrementally replace them with strict types.

  • Set Strict Compiler Options Gradually: Enable strict TS options (like noImplicitAny) in stages to avoid overwhelming the team or breaking the codebase all at once.

    • Example change in tsconfig.json:
    {
      "compilerOptions": {
        "strict": false
      }
    }
    

    Upgrade to "strict": true only after initial migration stabilizes.

  • Automate and Use Type Declaration Tools: Tools like @types packages (for external dependencies) and ts-migrate or ts-migrate-full can automate parts of the process.

  • Invest in Team Training: Provide learning resources, pair programming, and code reviews to help the team adopt TypeScript effectively.

  • Write or Update Tests: Use tests to catch bugs during migration and ensure everything keeps working as expected.

  • Monitor Performance: Measure the impact on build times and editor responsiveness, and adjust tooling settings as needed.

Conclusion

Migrating a large JavaScript codebase requires thoughtful planning, incremental changes, and a focus on developer support. By anticipating challenges and following best practices, teams can make the process smoother and unlock the productivity and safety benefits of TypeScript.

Decostruire lo Streaming di FC2: Come Costruire un Downloader ad Alte Prestazioni con HLS e WebAssembly

Per l’utente comune, “scaricare un video” sembra una semplice questione di trovare un link .mp4. Tuttavia, per gli sviluppatori che lavorano con piattaforme legacy ma massicce come FC2 Video, la realtà è un’infrastruttura frammentata, protetta e ottimizzata tramite il protocollo HLS (HTTP Live Streaming).
Nello sviluppo del Downloader Video FC2, abbiamo affrontato sfide tecniche che vanno ben oltre il semplice web scraping. In questo articolo, analizzerò l’architettura di distribuzione video di FC2 e le soluzioni ingegneristiche che abbiamo implementato per ottenere un’estrazione lossless ad alta velocità.

1. La Sfida Centrale: Il Video “Invisibile”

FC2 non serve file video statici. Utilizza un’infrastruttura di Adaptive Bitrate Streaming (ABS).
1.1 L’Architettura Frammentata HLS
A differenza dei classici file MP4, lo standard HLS divide il video in migliaia di piccoli segmenti.
• Master Playlist (.m3u8): Un file manifest che elenca tutte le risoluzioni disponibili (1080p, 720p, ecc.).
• Media Playlist: Un sotto-manifest per una risoluzione specifica contenente gli URL dei singoli segmenti video (solitamente file .ts di pochi secondi).
1.2 Barriere di Autenticazione: Token Dinamici
L’endpoint di FC2 richiede parametri di sessione unici. Tentare di accedere a un segmento .ts senza la firma corretta o senza i cookie appropriati genera sistematicamente un errore 403 Forbidden. Il nostro motore deve emulare l’intero “handshake” tra il player ufficiale e il backend di FC2 per ottenere i diritti di accesso in tempo reale.

2. Architettura: Muxing Client-Side con WebAssembly (WASM)

Tradizionalmente, i downloader inviano i flussi a un server centrale per unirli tramite FFmpeg. Questo è inefficiente, costoso in termini di banda e pone problemi di privacy. Abbiamo scelto un approccio Client-Side First.
2.1 L’Utilizzo di FFmpeg.wasm
Nel nostro strumento, abbiamo spostato il carico di lavoro sul browser dell’utente grazie a WebAssembly (WASM).
• Remuxing Lossless: Utilizziamo l’argomento -c copy. Questo non ricodifica il video (operazione che richiederebbe tempo e degraderebbe la qualità), ma cambia semplicemente il “container” dal flusso TS a MP4.
• Privacy by Design: Poiché l’unione avviene nella RAM del browser dell’utente, il contenuto video non tocca mai i nostri server.

3. Risolvere l’Ostacolo del CORS (Cross-Origin Resource Sharing)

Le politiche di sicurezza dei browser impediscono a uno script sul nostro dominio di recuperare dati binari direttamente dai server di contenuto di FC2.
3.1 La Soluzione del Proxy di Streaming Trasparente
Abbiamo progettato un proxy ad alte prestazioni in Node.js che funge da ponte:

  1. Il client richiede un segmento tramite il nostro proxy.
  2. Il proxy rimuove gli header CORS restrittivi di FC2 e inietta Access-Control-Allow-Origin: *.
  3. Stream Piping: I dati transitano senza essere memorizzati sul server, garantendo un utilizzo minimo della RAM e una latenza quasi nulla.

4. Ottimizzazione: Parallelizzazione e Allineamento Dati

Scaricare 500 segmenti in modo sequenziale è un collo di bottiglia. Abbiamo implementato un Async Promise Pool per mantenere tra 5 e 10 download simultanei, massimizzando la banda senza attivare le protezioni anti-DDoS del CDN.
4.1 Correzione dei Timestamp (PTS)
Talvolta, i segmenti HLS su FC2 presentano discontinuità nei Presentation Time Stamps (PTS). Se uniti brutalmente, il video finale potrebbe presentare desincronizzazioni audio-video. Il nostro motore scansiona gli header TS prima dell’unione per riallineare i timestamp, garantendo un file MP4 finale fluido.

5. Conclusione: Ingegneria al Servizio dell’Utente

Costruire un downloader per FC2 è un esercizio di stile nell’architettura web moderna. Combinando proxy trasparenti, il parsing HLS e la potenza di WebAssembly, abbiamo creato uno strumento rapido, efficiente e rispettoso della privacy.
Se stai cercando un modo affidabile per salvare i tuoi contenuti FC2 in qualità originale 1080p, prova la nostra soluzione: 👉 Downloader Video FC2
Punti di Forza Tecnici:
• Qualità Nativa: Nessuna compressione, copia 1:1 del flusso originale.
• Potenziato da WASM: Elaborazione locale per una velocità e sicurezza superiori.
• Zero Installazione: Tutto avviene nel browser grazie agli standard web moderni.
Hai mai usato WebAssembly per manipolare file multimediali nel browser? Condividi le tue esperienze nei commenti!

Tags: #JavaScript #WebDev #NodeJS #WebAssembly #FFmpeg #FC2 #Streaming #SoftwareArchitecture

Top 10 Agentic AI Frameworks Compared: LangGraph vs CrewAI vs AutoGen vs… (Benchmarks Inside)

I spent six weeks running identical tasks through ten different frameworks so you don’t have to argue about this in Slack anymore.

There’s a conversation that happens in almost every engineering team building agents right now. Someone says “we should use LangChain.” Someone else says “CrewAI is better for multi-agent stuff.” A third person asks if anyone has looked at AutoGen. Nobody can agree because everyone is going off demos, blog posts and vibes.

I got tired of that conversation, so I ran actual benchmarks.

Six weeks, ten frameworks, five evaluation tasks repeated consistently across all of them. The tasks were chosen to reflect what production agent systems actually need to do, not what looks impressive in a README.

Here’s what I tested and what I found.

The Benchmark Setup

Five evaluation dimensions:

Agent setup time : how long from pip install to a working agent with tool use. Measured in minutes, not “effort.”

Tool integration complexity : how many lines of code to add a custom tool that calls an external API.

Multi-agent orchestration : can it coordinate multiple specialised agents? How cleanly?

Memory handling : does it support conversation memory and persistent context across sessions?

Error recovery : what happens when a tool call fails or returns unexpected output?

Hardware: M3 MacBook Pro, 32GB. All tests used Claude Sonnet as the underlying model via API for consistency. I’m not benchmarking model quality, I’m benchmarking framework overhead and developer experience.

The Frameworks

LangGraph, CrewAI, AutoGen, LlamaIndex Workflows, Haystack, OpenClaw, Semantic Kernel, Phidata, Pydantic AI and AgentOps.

Let’s go through them.

1. LangGraph

Setup time: 18 minutes | Tool integration: Medium | Multi-agent: Excellent | Memory: Good | Error recovery: Excellent

LangGraph is the framework I’d recommend to engineers who think in graphs. The mental model, nodes are processing steps, edges are transitions, state flows through the graph, is powerful once it clicks and slightly alien until it does.

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    messages: Annotated[list, operator.add]
    tool_results: list
    next_action: str

def research_node(state: AgentState):
    # Agent reasoning step
    return {"messages": [{"role": "assistant", "content": "Researching..."}]}

def tool_node(state: AgentState):
    # Tool execution step  
    return {"tool_results": ["result_data"]}

workflow = StateGraph(AgentState)
workflow.add_node("research", research_node)
workflow.add_node("tools", tool_node)
workflow.add_edge("research", "tools")
workflow.add_edge("tools", END)

app = workflow.compile()

The error recovery is genuinely impressive. You can define explicit fallback edges, if this node fails, route here instead, which produces resilient agent behaviour without try/except spaghetti scattered throughout your application code.

The trade-off: the learning curve is real. Engineers who aren’t comfortable with graph-based thinking will fight the abstraction. Setup time reflects this, 18 minutes because I kept second-guessing the state schema design.

2. CrewAI

Setup time: 8 minutes | Tool integration: Easy | Multi-agent: Excellent | Memory: Good | Error recovery: Medium

CrewAI has the most intuitive API of any framework on this list for multi-agent work. The Role-Task-Crew mental model maps directly to how you’d describe the work to another engineer.

from crewai import Agent, Task, Crew, Process

researcher = Agent(
    role="Research Analyst",
    goal="Find accurate information about {topic}",
    backstory="Expert at synthesising complex information",
    verbose=True,
    tools=[search_tool, web_scraper_tool]
)

writer = Agent(
    role="Technical Writer",
    goal="Write clear documentation from research",
    backstory="Turns technical findings into readable content"
)

research_task = Task(
    description="Research {topic} and identify key technical details",
    agent=researcher,
    expected_output="Structured research summary with sources"
)

write_task = Task(
    description="Write documentation based on the research",
    agent=writer,
    expected_output="Complete technical document"
)

crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, write_task],
    process=Process.sequential
)

result = crew.kickoff(inputs={"topic": "MCP protocol"})

8 minutes to a working multi-agent system. That’s impressive and it reflects how well-designed the abstractions are.

The error recovery is where CrewAI shows its youth. When tools fail, the default behaviour is for the agent to retry with the same approach, which sometimes loops rather than adapts. You can override this with custom callbacks but it requires more configuration than LangGraph’s graph-native error routing.

3. AutoGen

Setup time: 22 minutes | Tool integration: Medium | Multi-agent: Excellent | Memory: Medium | Error recovery: Good

AutoGen is Microsoft’s framework and it shows, in the best possible way. The conversational multi-agent pattern, where agents literally message each other to collaborate, is different from CrewAI’s task assignment model and genuinely powerful for complex reasoning chains.

import autogen

config_list = [{"model": "claude-sonnet-4-5", "api_key": "your_key"}]

assistant = autogen.AssistantAgent(
    name="assistant",
    llm_config={"config_list": config_list},
    system_message="You are a helpful coding assistant."
)

code_reviewer = autogen.AssistantAgent(
    name="code_reviewer", 
    llm_config={"config_list": config_list},
    system_message="You review code for bugs and improvements."
)

user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    human_input_mode="NEVER",
    code_execution_config={
        "work_dir": "coding",
        "use_docker": False
    }
)

# Start a multi-agent conversation
user_proxy.initiate_chat(
    assistant,
    message="Write a Python function to parse nested JSON"
)

The code execution capability, agents can write and run code in a sandboxed environment, is genuinely useful and something not every framework handles this cleanly.

The 22-minute setup time reflects the Azure OpenAI configuration options and the number of agent parameters. Not complex, just verbose.

4. LlamaIndex Workflows

Setup time: 15 minutes | Tool integration: Easy | Multi-agent: Good | Memory: Excellent | Error recovery: Medium

LlamaIndex has the best RAG integration of any framework here — which makes sense given its origins. If your agent needs to reason over large document collections, LlamaIndex Workflows is the framework that handles this without bolted-on complexity.

LlamaIndex has the best RAG integration of any framework here, which makes sense given its origins. If your agent needs to reason over large document collections, LlamaIndex Workflows is the framework that handles this without bolted-on complexity.

from llama_index.core.workflow import Workflow, StartEvent, StopEvent, step, Event

class ResearchEvent(Event):
    query: str

class AnalysisEvent(Event):
    research_results: str

class ResearchWorkflow(Workflow):
    @step
    async def research(self, ev: StartEvent) -> ResearchEvent:
        # Query documents and retrieve context
        results = await self.query_index(ev.query)
        return ResearchEvent(query=ev.query)

    @step
    async def analyse(self, ev: ResearchEvent) -> StopEvent:
        # Synthesise the research
        analysis = await self.synthesise(ev.query)
        return StopEvent(result=analysis)

workflow = ResearchWorkflow(timeout=60, verbose=True)
result = await workflow.run(query="Explain the MCP protocol")

The event-driven architecture is clean once you understand it. The memory handling, particularly for RAG-heavy workloads, is the best on this list.

Where it falls short: the multi-agent orchestration requires more manual wiring than CrewAI or AutoGen. It’s capable, but you’re doing more of the coordination work yourself.

5. Haystack

Setup time: 12 minutes | Tool integration: Easy | Multi-agent: Good | Memory: Good | Error recovery: Good

Haystack’s pipeline-based architecture is the most auditable of any framework here. Every processing step is explicit, the data flow is visible and the system is straightforward to debug when something goes wrong.

from haystack import Pipeline
from haystack.components.generators import AnthropicGenerator
from haystack.components.routers import MetadataRouter

pipeline = Pipeline()
pipeline.add_component("router", MetadataRouter(rules={
    "search": {"task": {"$eq": "search"}},
    "analysis": {"task": {"$eq": "analysis"}}
}))
pipeline.add_component("search_agent", AnthropicGenerator(
    model="claude-sonnet-4-5"
))
pipeline.add_component("analysis_agent", AnthropicGenerator(
    model="claude-sonnet-4-5"
))

pipeline.connect("router.search", "search_agent.prompt")
pipeline.connect("router.analysis", "analysis_agent.prompt")

For teams with compliance or audit requirements, the explicit pipeline structure makes Haystack genuinely preferable to more opaque frameworks. You can answer “what did this agent do and why” clearly from the pipeline logs.

The trade-off: less dynamic than graph-based frameworks. Complex conditional reasoning is harder to express in pipeline terms.

6. OpenClaw

Setup time: 25 minutes | Tool integration: Medium | Multi-agent: Good | Memory: Good | Error recovery: Good

OpenClaw is the self-hosted option on this list and the one worth knowing about if data privacy is a requirement. No API calls to external services, everything runs in your infrastructure.

from openclaw import Agent, LocalLLM, Tool

llm = LocalLLM(
    model_path="./models/llama-3.1-70b-q4",
    context_length=8192
)

@Tool.register("database_query")
async def query_db(sql: str) -> dict:
    conn = await get_db_connection()
    result = await conn.execute(sql)
    return {"rows": result.fetchall()}

agent = Agent(
    llm=llm,
    tools=["database_query"],
    system_prompt="You are a data analysis assistant.",
    memory_enabled=True
)

response = await agent.run(
    "Analyse the monthly revenue trend from the sales table"
)

The 25-minute setup reflects model download and local configuration. Once running, the performance is solid for its class.

The honest trade-off: the model capability ceiling is lower than API-based frameworks unless you have significant local compute. For use cases where data residency matters more than peak performance, it’s worth it. For OpenClaw’s full architecture and where it fits in the self-hosted landscape, the Dextra deep-dive covers what a README can’t.

7. Semantic Kernel

Setup time: 20 minutes | Tool integration: Easy | Multi-agent: Good | Memory: Good | Error recovery: Good

Microsoft’s other agent framework. Where AutoGen is built around conversational multi-agent patterns, Semantic Kernel is built around plugins and planners, a more structured, less conversational approach.

import semantic_kernel as sk
from semantic_kernel.connectors.ai.anthropic import AnthropicChatCompletion

kernel = sk.Kernel()
kernel.add_service(AnthropicChatCompletion(
    ai_model_id="claude-sonnet-4-5",
    api_key="your_key"
))

@sk.kernel_function(name="analyse_data", description="Analyse dataset")
async def analyse_data(kernel: sk.Kernel, data: str) -> str:
    return f"Analysis of: {data}"

kernel.add_function(plugin_name="DataPlugin", function=analyse_data)

The .NET-first heritage shows in the C# documentation being significantly better than the Python docs. If your team works in .NET, Semantic Kernel is the clear choice. For Python-first teams, it’s capable but requires tolerance for occasionally thin Python documentation.

8. Phidata

Setup time: 6 minutes | Tool integration: Very easy | Multi-agent: Good | Memory: Excellent | Error recovery: Medium

Phidata has the fastest setup time on this list, six minutes is genuinely six minutes and the built-in storage integrations (PostgreSQL, SQLite, Redis) for agent memory are better than almost any other framework here out of the box.

from phi.agent import Agent
from phi.model.anthropic import Claude
from phi.tools.duckduckgo import DuckDuckGo
from phi.storage.agent.sqlite import SqlAgentStorage

agent = Agent(
    model=Claude(id="claude-sonnet-4-5"),
    tools=[DuckDuckGo()],
    storage=SqlAgentStorage(
        table_name="agent_sessions",
        db_file="agent_memory.db"
    ),
    add_history_to_messages=True,
    num_history_responses=5,
    show_tool_calls=True
)

agent.print_response("What are the latest developments in MCP?")

The trade-off for the fast setup: less flexibility for complex custom orchestration. Phidata is excellent for building agents quickly with solid memory. It’s less suited for intricate multi-agent coordination patterns.

9. Pydantic AI

Setup time: 10 minutes | Tool integration: Very easy | Multi-agent: Medium | Memory: Medium | Error recovery: Excellent

If you already use Pydantic (and most Python developers do), Pydantic AI’s mental model will feel immediately familiar. The typed output validation is the best of any framework here, if your agent produces structured data, Pydantic AI guarantees it conforms to your schema.

from pydantic_ai import Agent
from pydantic import BaseModel

class AnalysisResult(BaseModel):
    summary: str
    key_findings: list[str]
    confidence_score: float
    recommendations: list[str]

agent = Agent(
    'claude-sonnet-4-5',
    result_type=AnalysisResult,
    system_prompt="Analyse the provided data and return structured insights."
)

result = await agent.run("Analyse Q3 2025 performance metrics: revenue up 23%...")
print(result.data.key_findings)  # Guaranteed to be a list[str]
print(result.data.confidence_score)  # Guaranteed to be a float

The error recovery is excellent specifically because validation happens at the framework level, not just at the application level. If the model produces output that doesn’t match the schema, Pydantic AI retries automatically with corrective context.

The multi-agent orchestration is the weak point. It’s not impossible but it requires more manual coordination than dedicated multi-agent frameworks.

10. AgentOps

Setup time: 14 minutes | Tool integration: Medium | Multi-agent: Good | Memory: Medium | Error recovery: Good

AgentOps is different from the others, it’s less a standalone framework and more an observability and orchestration layer that wraps other frameworks. If you’re already using LangGraph or CrewAI and need production monitoring, cost tracking and session replay, AgentOps is the integration to look at.

import agentops
from agentops import track_agent, record_tool

agentops.init(api_key="your_key")

@track_agent(name="research_agent")
class ResearchAgent:
    @record_tool("web_search")
    async def search(self, query: str) -> str:
        results = await perform_search(query)
        return results

    async def run(self, task: str):
        research = await self.search(task)
        return research

agent = ResearchAgent()
result = await agent.run("Research agentic AI frameworks")
agentops.end_session("Success")

In production, the cost per session tracking alone makes this worth evaluating. Knowing which agent workflows are burning tokens without producing value is information you need and that most frameworks don’t surface cleanly.

The Benchmark Summary

My Actual Recommendations

For a new production agent system: LangGraph. The learning curve is real, the error recovery and state management are worth it at scale.

For a team that needs something working this week: CrewAI. The time-to-working-system is the best of the serious frameworks.

For document-heavy RAG agent work: LlamaIndex Workflows. Nothing else handles this as naturally.

For regulated environments needing audit trails: Haystack. The pipeline explicitness isn’t a limitation, it’s the feature.

For self-hosted with data privacy requirements: OpenClaw. The setup overhead is the price of keeping data in your infrastructure.

The full breakdown with additional benchmarks on the top agentic AI frameworks in 2026 is published for teams who want more than fits in a single article.

If you’re evaluating open-source options specifically, OpenClaw is worth a look for self-hosted use cases. We published a deep-dive on its architecture, deployment patterns and where it fits relative to the managed alternatives.

Published by Dextra Labs | AI Consulting & Enterprise Agent Development

The Hybrid Method: when Claude.ai supervises Claude Code

The Hybrid Method is an organization of work in which the user stays in Claude.ai, the conversational chat interface, while long-running engineering tasks are delegated in the background to Claude Code, the command-line autonomous agent. The delegation is invisible: the chat itself writes the professional engineering prompt adapted to the situation, launches the executor, supervises its work through the local filesystem and the git history, and reports the result back to the user in the same conversation. The user never opens a terminal, never types a prompt for Claude Code, never leaves the chat.

The name follows the automotive analogy. A hybrid car coordinates two engines with complementary strengths — a combustion engine for sustained load, an electric engine for instant response — through a transmission that selects which one is engaged at each moment. Claude.ai and Claude Code map onto these two engines structurally. The driver presses one pedal; the system handles the allocation underneath.

I have been using this method daily on projects in production since April 21, 2026. After a month of measured use, it is the most useful AI-assisted engineering organization I have encountered: throughput gain of approximately an order of magnitude over single-agent use, higher output quality, and a context-switching cost close to zero because the user never leaves the conversational interface. As of May 20, 2026, a review of the public literature on the Claude ecosystem indicates that this exact assembly is not documented anywhere — the individual pieces are public, the combination appears to be new. That is what motivates this article.

1. The context that makes this method necessary

Claude.ai and Claude Code cover different work regimes. Claude.ai is a conversational interface — web or desktop — suited to decisions, exploration, document work, and short tool calls through Model Context Protocol (MCP) servers. Claude Code is a command-line agent suited to autonomous engineering work over hours at a time: reading codebases, running tests, refactoring, committing.

Most developers use the two tools separately. They reach for the chat to think and decide, and for Claude Code to build. This separation has a hidden cost. When the work to be done exceeds what the chat can do comfortably — a multi-file refactor, an audit followed by structured corrections, a multi-hour sprint with tests and tagged releases — the developer becomes the orchestrator between the two interfaces. They hold the plan in their head, switch between contexts, re-prompt every fifteen minutes, supervise every step. Three hours of Claude Code’s work cost three hours of the developer’s attention.

For users without a strong prompt engineering practice, the situation is worse. The prompts they can write for Claude Code extract only a fraction of what the tool can produce in autonomous mode. Claude Code’s agentic mode expects a structured brief most users do not know how to write: explicit phases, acceptance criteria, operational rules, escalation protocol. Without that brief, the tool underperforms or drifts.

The Hybrid Method resolves both problems through the same mechanism: it delegates the engineered prompt writing AND the execution to the system, while keeping the user in their single conversational environment. The chat translates the user’s natural-language intent into a structured engineering brief, hands it to the executor, supervises the work, and returns a natural-language summary. The user’s contribution is restricted to expressing intent, reviewing the plan, and validating the outcome — which is the work only the user can do.

2. Principle and description of the method

The Hybrid Method runs two distinct Claude agents on the same project — one conversational, one executor — and treats them as functionally distinct engines coordinated by a small transmission layer.

The conversational engine is Claude.ai. It is the interface the user already uses to think, to ask, to decide, to draft. It excels at conversation, at intent disambiguation, at long shared context with the user, at single MCP tool calls. It does not sustain hours of autonomous filesystem work. That is not what the chat is for.

The executor engine is Claude Code, the command-line agent provided by Anthropic. Once given a structured prompt and a permission profile, it operates autonomously: it explores the repository, plans, writes, tests, commits, fixes, iterates, until the prompt’s acceptance criteria are met or the work is blocked. It does not require human turn-taking during execution. It is unsuited to short conversational exchanges.

Neither agent alone covers the full range of work in a serious software project. The Hybrid Method runs both, with the conversational engine acting as supervisor of the executor. When the user describes a goal that exceeds the conversational lane’s capacity, the chat translates that goal into a structured engineering prompt for the executor, launches the executor as a background process, polls its progress periodically, intervenes when necessary, and reports the outcome back to the user in natural language. The user never opens a terminal. The user never writes a prompt for Claude Code. The user stays in the chat.

The automotive analogy gives the method its name and clarifies its underlying logic. A hybrid car runs two engines simultaneously: an internal combustion engine that sustains long-distance work under heavy load but has a measurable startup cost, and an electric engine that starts instantly and handles low-load, short-distance work efficiently. A combustion engine started for a short trip wastes more fuel in startup than the trip itself requires. An electric engine asked to handle a long-haul drive becomes the limiting factor. A hybrid car does not choose. It runs both, and a transmission system selects the engine that is engaged at any given moment based on the conditions of the trip. The driver presses one pedal; the system manages the allocation underneath.

Claude.ai and Claude Code map onto these two engines structurally, not figuratively. Claude.ai has the properties of the electric engine: instant startup because the user is already in the chat session, excellent low-load handling, smooth transitions, and the user-facing interface where decisions live. Claude Code has the properties of the combustion engine: a measurable cold-start cost (typically 5 to 15 seconds while it loads the project’s memory file and explores the repository), sustained autonomous performance once running, native access to file systems and version control. The asymmetry of strengths and costs between the two engines is what makes their coordination valuable.

Property Claude.ai (conversational engine) Claude Code (executor engine)
Startup cost Negligible (existing chat session) 5–15 seconds (cold context, repo exploration)
Sustained autonomous work Bounded per conversational turn Multi-hour
User context retention Excellent (long chat memory) Per-session, reset between runs
Repository-scale operations Slow via MCP, bounded by tool calls Native, unbounded
Conversational quality Optimized for it Not its purpose
Cost per unit of useful work Lower for short tasks Lower for long tasks

Used alone, each engine has a regime where it dominates and a regime where it is the wrong choice. Used together, with an explicit allocation policy and a small transmission layer, they cover the full operational envelope of a real software project.

3. Technical architecture

The transmission layer between the two engines does not rely on any custom communication protocol. It is built from three components that already exist on any standard development machine: the local filesystem, the version control system, and a small MCP server that exposes a Bash tool to the conversational engine.

The MCP server is the only piece that needs to be installed and configured. Roughly two hundred lines of Python using Anthropic’s official MCP Python SDK are sufficient. Several community implementations are also available. The server exposes a tool — call it bash_local — that allows the conversational engine to execute shell commands on the host machine. This tool is the bridge between the two engines: the conversational engine uses it to launch the executor as a background subprocess, and later to poll status files and read the git log.

The launch command itself is a standard nohup invocation that spawns Claude Code with a multi-page engineered prompt on standard input:

nohup bash -c 'echo "$PROMPT" | claude -p 
  --model opus-4-7 --effort xhigh 
  --dangerously-skip-permissions 
  > /tmp/sprint_run.log 2>&1' &

Once the executor is launched, the two engines communicate exclusively through artifacts written to the filesystem. The executor maintains a progress file (typically at /tmp/sprint_progress.md) that reflects its current state in human-readable form. If blocked, it writes a blocking file (/tmp/sprint_blocked.md) and proceeds to the next subtask. Commits are pushed to the git repository as work is completed. The conversational engine, at intervals of five to ten minutes, polls these three sources — the progress file, the existence of a blocking file, the last entries of git log — through its bash_local tool. No callbacks, no streaming, no WebSocket connection. The filesystem is the channel; git is the distributed clock.

A subtle but important component of the architecture is the project memory file conventionally named CLAUDE.md. Anthropic built into Claude Code the convention that any file with this name at the root of the working directory is automatically loaded into context at the start of every session. It serves as the executor’s persistent project memory.

In the Hybrid Method, the conversational engine also reads this same file — explicitly, through its bash_local tool, at the start of any project-related conversation. Both engines therefore begin from the same shared understanding of the project: its identity, its current state, its architectural conventions, the strict rules governing what each engine is allowed and forbidden to do, the required reading list, the escalation protocol. This shared memory is what makes the transmission reliable. When the conversational engine writes a sprint prompt for the executor, it does not need to repeat any project context — the executor already has it from CLAUDE.md. The prompt can focus entirely on the specifics of the current sprint: which subtasks, with what acceptance criteria, in what order, with what operational rules. A well-maintained CLAUDE.md is the most underrated artifact in this configuration.

Schematically, the data flow is the following:

User (natural language)
   │
   ▼
Claude.ai (conversational engine)
   │   uses MCP tool: bash_local
   │   launches: nohup claude -p ... &
   ▼
Claude Code (executor engine, background subprocess)
   │   reads CLAUDE.md automatically
   │   reads engineered prompt from stdin
   │   works autonomously for 1–3 hours
   │   commits and pushes incrementally
   ▼
Git repository (single source of truth)
   │   Claude.ai polls every 5–10 minutes:
   │     - last entries of git log
   │     - /tmp/sprint_progress.md
   │     - /tmp/sprint_blocked.md (if present)
   ▼
Claude.ai reports back to user in natural language

Nothing in this architecture requires a cloud service, a custom transport protocol, or a software development kit beyond what the two Claude products already provide.

4. The task allocation matrix

The transmission layer described in the previous section is only economically rational if there is a clear policy for which engine is engaged for which kind of work. The wrong policy wastes either the responsiveness of the conversational engine or the autonomy of the executor.

The matrix below is the policy I have settled on after a month of measurement in daily production use. It is empirical rather than theoretical: each row reflects a regime in which one engine is measurably more efficient than the other.

Task profile Engine engaged Justification
Less than 5 minutes, single-purpose (a one-line fix, a targeted SQL update, a service restart) Conversational The executor’s cold-start cost exceeds the work itself. Engaging the chat through its existing MCP tools is faster end-to-end.
5 to 15 minutes, multi-file, conversational context already loaded Conversational The context is hot. Re-loading it into a fresh executor session would cost more than executing directly through MCP tool calls.
5 to 15 minutes, multi-file, cold context Executor (foreground) Cold context means the executor is faster than re-explaining everything to the chat through MCP.
More than 15 minutes, or open-ended exploration Executor (background) Launched as nohup claude -p ... &. The chat polls progress files and the git log. The user is freed for other work.
Multi-module refactor, cross-cutting changes Executor Native grep, read, and write across the repository without MCP round-trips.
Document, article, or strategy synthesis Conversational Narrative structure is conversational. The executor treats narrative as a side task; the chat treats it as its primary mode.
Audit, plan, decide Conversational, sometimes assisted by a read-only executor fact-finding pass The chat holds the strategic context. If specific facts about the codebase are needed, it spawns a read-only executor pass first, then reasons over the result.

The cost asymmetry that drives this matrix is empirical. A cold Claude Code session has a measurable startup time of approximately 5 to 15 seconds before any useful work happens, plus the time required to load CLAUDE.md and explore the relevant directories. For a task whose entire useful work is 30 seconds, that startup dominates the total elapsed time. For a task whose useful work is 30 minutes, it is negligible. The matrix simply tracks the crossover point.

This allocation policy is the substantive intellectual content of the Hybrid Method. The transmission infrastructure — a Bash tool exposed through MCP, status files in a known location, git as the source of truth — is mechanical and obvious once described. The discipline of knowing when to engage which engine is what makes the configuration economically rational rather than wasteful, and it is what cannot be improvised. It is built through measurement.

5. Deployment: local or server

The Hybrid Method is neutral to the location of the executor. The same pattern operates without modification in two configurations.

In the local configuration, the executor runs as a subprocess on the developer’s own machine. A local MCP server (a small Python program installed on the developer’s laptop) exposes the Bash tool that the conversational engine uses to launch and supervise. This is the configuration most appropriate for personal development on personal hardware. No sandboxing is necessary because the developer is working on their own files. This is the configuration in which the illustration discussed in section 7 took place.

In the server configuration, the executor runs on a Linux server under a dedicated low-privilege Unix user, isolated from the rest of the system. The MCP server provides remote access — typically through SSH — allowing the conversational engine running on the user’s device to launch and supervise the executor remotely. This is the configuration most appropriate for continuous production work on systems whose state must persist across user sessions. It is the configuration I have used since April 21, 2026 across several projects.

The transmission layer — filesystem, git, MCP bridge — does not care where the executor lives. This neutrality of deployment is one of the properties that distinguish the Hybrid Method from the alternatives discussed in section 9: Cowork is bound to a managed virtual machine, AWS AgentCore is bound to AWS infrastructure, Slack delegation is bound to Anthropic’s cloud. The Hybrid Method is bound to nothing beyond standard MCP. It runs wherever the user chooses to run it.

For an agent operating on a non-developer user’s machine — for example, in the context of a consumer product that exposes Claude Code’s capabilities to users without engineering backgrounds — the Bash tool must be carefully sandboxed. That is a different conversation and is not the subject of this article.

6. Application in practice

The user-facing experience of the Hybrid Method is straightforward. The user opens Claude.ai, formulates an intent in natural language, and the conversational engine handles the rest.

A typical session begins with the user describing a goal. “The recent audit identified thirteen findings on the project. Eight are critical or high. Can we close them this evening across two or three tagged releases?” The conversational engine reads the audit report through its bash_local MCP tool, reads the project’s recent release history and CLAUDE.md, and proposes a plan in the same conversational language: which findings belong in which release, with what acceptance criteria, which to defer with documented justification. The user reviews the plan in the chat and approves.

The conversational engine then writes a multi-page engineered prompt for the executor. This prompt is structured in four phases — read, plan, implement, verify — with explicit acceptance criteria per subtask, with strict operational rules attached (no force-push, tests required before every commit, mandatory status file updates), and with an escalation protocol specifying what the executor should do if blocked for more than thirty minutes on a single subtask. The user does not read this prompt. The user trusts the writer.

The launch is invisible: the conversational engine calls its Bash tool, executes the nohup ... claude -p ... & invocation, and returns to the user with confirmation that the sprint is running. The user closes the laptop or moves on to other work. The executor is now alone with its prompt, its CLAUDE.md, and the codebase.

For the next several hours, the user is out of the loop. The conversational engine polls progress every five to ten minutes. If everything appears normal, no action is taken. If a regression appears in the commit stream, the conversational engine investigates: it reads the relevant diffs, evaluates whether the regression is real or apparent, and either patches in place or rolls back. When the work is complete, the conversational engine produces a summary in the user’s preferred language, with the list of commits, the closed findings, the deferred items with their justifications, any incidents that occurred and how they were handled, and a pointer to where the artifacts can be reviewed.

At no point in this session does the user open a terminal, type a prompt for Claude Code, or leave the Claude.ai interface. The user expresses intent in natural language at the start, reviews the proposed plan, validates the final result. The execution is delegated end to end.

This division of labor is the operational expression of a broader principle: that the user’s role in software engineering with AI assistance should be to decide and to validate, and the AI’s role should be to execute. The Hybrid Method makes this principle implementable in practice, on real projects, at the scale at which serious software is built.

7. Illustration: a multi-release sprint

To make the previous sections concrete, this section describes one specific application of the method. The case is representative rather than exceptional. The continuous use of the method on multiple projects produces similar outcomes; this particular sprint is simply compact enough to summarize in a single section.

The context: a macOS desktop product currently in pre-release, which I will not name here as the product is under embargo until its public launch. The state at the start of the sprint was version 1.0.7.1, shipped some days earlier. An external code audit, itself performed as a Claude Code session in xhigh-effort mode against a clean checkout, had produced a report of thirteen findings: one critical security gap, four high-priority items, the rest medium and low priority. The goal of the sprint was to close as many findings as feasible and ship the resulting work as tagged, changelog-documented releases.

The conversational engine planned three releases. Version 1.0.8 would close the critical security finding and three of the four high-priority items, plus a planned dashboard improvement. Version 1.0.9 would add a set of missing HTTP endpoints. Version 1.0.10 would archive dead code identified by the audit and complete the low-priority cleanups.

The result, measured at the end of the sprint:

  • eleven commits pushed to main, all signed
  • eight of the thirteen findings closed, five deferred with documented justification
  • test suite extended from 106 to 125 tests, all passing
  • three releases tagged: 1.0.8, 1.0.9, 1.0.10
  • one .pkg installer built and verified on the developer’s machine
  • no incremental API charges — Claude Code, in this setup, is authenticated through OAuth against my paid Claude subscription (Pro/Max plans, which cover both interactive Claude.ai and Claude Code usage on a single monthly fee); the sprint’s consumption drew from that subscription’s quota, not from a pay-per-token API account
  • approximately thirty minutes of human typing time, distributed between planning at the start and review at the end

One operational incident occurred during the sprint and is worth documenting because the response illustrates the supervision pattern. A cleanup operation moved a directory src/wrappers/ to src/legacy/wrappers/. Within the executor’s working scope this was correct: imports were updated, the test suite passed, the commit was clean. Outside the executor’s scope, on the developer’s personal machine, three entries of a local hybrid testing configuration pointed into src/wrappers/ and silently broke when the files moved on disk. The product itself was unaffected (the installed application is self-contained), but the developer’s local environment was. The conversational engine, polling the commit stream, recognized the implication for the local configuration that had been loaded as context earlier, patched the configuration with a one-line sed, backed up the previous version, and noted the incident in the progress file.

From the incident, a new operational rule was derived and added to the project’s CLAUDE.md: the executor produces installable artifacts (.pkg files) and never modifies the developer’s working copy or personal configuration. Future sprints work on dedicated branches or in temporary clones. This rule now applies to every subsequent session.

The illustration is not a remarkable result; it is a representative one. What it demonstrates is that the method produces measured outputs that are competitive in quality with what a small engineering team would produce over a similar duration, with one supervising human engaged for tens of minutes rather than several hours.

8. What the method changes for senior engineers and for users without an engineering background

The Hybrid Method addresses two distinct constraints that have historically limited what an individual can produce with AI assistance, depending on the individual’s level of engineering background.

For senior engineers, the persistent constraint with single-agent AI coding tools is attention rather than capability. The tool can write code faster than the engineer can type, but the engineer still has to hold the plan, supervise every step, review every commit, decide what to do when the tool gets confused. The total work product per unit of engineer-time is bounded by the engineer’s ability to remain present in the execution loop. The Hybrid Method shifts the engineer’s attention to a different lane. The strategic work — architecture, scope, trade-offs, validation — stays with the engineer, but is held in conversation with the conversational engine, which can read the codebase and surface specific facts on demand. The execution work moves to the executor under the conversational engine’s supervision. The line-by-line review work also moves to the conversational engine, which reads the commit stream as it arrives and flags anomalies. The engineer’s residual attention is concentrated on judgment and validation, which is the work that only the engineer can do. A senior engineer using this method can credibly maintain several active sprints in parallel, because none of them requires continuous attention during execution.

For users without a software engineering background — professionals in other fields, technical hobbyists, anyone whose primary expertise is elsewhere — the persistent constraint with single-agent AI coding tools has been prompt-craft expertise. Writing the multi-page structured prompt that Claude Code needs to produce high-quality autonomous output is itself a craft requiring weeks or months of accumulated engineering judgment. Most users without this background do not have it and never will. The Hybrid Method removes the requirement. The conversational engine writes the engineered prompt, drawing on its long conversational context with the user and on the project’s CLAUDE.md. The user expresses intent in natural language, in their own language; the rigorous prompt is produced for them. The user contributes domain knowledge, judgment about what the system should do, validation of outputs in the real-world context the user uniquely understands. The AI agents contribute the engineering rigor. The asymmetry that previously excluded non-engineers from building serious software is reduced.

The two effects are complementary. The same method that frees the senior engineer’s attention for higher-leverage work also opens the practice of software development to users whose previous bar to entry was prompt-craft expertise they did not possess. Neither effect was the explicit design goal; both are properties that emerge from the architectural choice of separating the conversational lane from the execution lane.

9. Why this assembly does not appear documented elsewhere

A review of the public literature on the Claude ecosystem as of May 20, 2026 identified several adjacent patterns. None of them is the same as the Hybrid Method described here.

Pattern Brief description How it differs
Operator / orchestrator pattern (MindStudio, Anthropic documentation) A Claude Code session orchestrates other Claude Code subagents through the native Task tool Internal to Claude Code. No conversational supervisor in front.
Claude Agent SDK (Anthropic, official) A Python wrapper that spawns Claude Code as a subprocess and drives it programmatically Programmatic orchestration. No natural-language interface for the human user.
Cowork (Anthropic, enterprise product) Claude Desktop spawns Claude in a managed sandboxed Linux virtual machine for asynchronous knowledge work Cloud-managed runtime. Visible to the user as a distinct product surface. Not a chat that delegates underneath.
Slack delegation (Anthropic) @Claude in a Slack thread launches a Claude Code session on Anthropic infrastructure Cloud-managed. Visible via the explicit mention. Not portable across organizations.
Code tab plus git worktrees (Claude Desktop, third-party guides) The desktop app exposes a user interface for spawning parallel Claude Code instances in worktrees Launched explicitly by the human user through a UI. No supervising agent in front of the executor.
AWS AgentCore with A2A protocol (third-party) A main agent and a code agent communicate over the A2A protocol on AWS AgentCore Cloud-based. Asynchronous over the A2A protocol. Not local; not MCP-based.

The Hybrid Method is differentiated from these adjacent patterns on four properties simultaneously. First, the supervisor is a conversational chat — not a script, not a piece of code, not a separate user interface — and the user interacts only with that chat. Second, the transport between the supervisor and the executor is standard MCP, not a Python wrapper or a custom protocol; any LLM client supporting MCP tools could play the supervisor role. Third, the executor is invisible to the user; Claude Code is a subcontractor of the chat, never surfaced explicitly. Fourth, the communication between the two engines is asynchronous and runs through the local filesystem and a git repository, which are the same artifacts that human teams already use for distributed software development.

The individual pieces of this assembly all exist publicly and have been documented separately. None of the documented patterns combines all four properties. This combination is what I have not found written down, and what motivates writing it down here.

10. How to implement the method

The Hybrid Method requires four pieces of infrastructure and three operational disciplines. The infrastructure can be assembled in an afternoon. The disciplines develop over weeks of practice.

The infrastructure consists of:

  • A Claude.ai subscription with MCP support (Pro or Max). This is the conversational engine.
  • A local MCP server that exposes a Bash tool and a few file utilities to the conversational engine. Approximately two hundred lines of Python written against Anthropic’s official MCP Python SDK, or one of the community implementations. No sandboxing is required for personal development on personal hardware. For an agent operating on a non-developer user’s machine, careful sandboxing is non-negotiable.
  • Claude Code installed locally (npm install -g @anthropic-ai/claude-code or via Homebrew on macOS) with a one-time login. This is the executor engine.
  • A well-maintained CLAUDE.md file at the root of the project repository. This is the shared memory. The first version takes about an hour to write. The file is refined over weeks of use.

The disciplines consist of:

  • A documented task allocation matrix. The version in section 4 of this article is mine; an implementer’s version may differ in details. What matters is that the matrix is written down, applied consistently, and updated based on measurement.
  • A prompt engineering practice for the engineered sprint prompts the conversational engine writes for the executor. Four phases, acceptance criteria per subtask, ten or so strict operational rules, an escalation protocol, status file conventions. This is the operational moat in the method. It is developed over weeks of iteration on real sprints.
  • The discipline of two lanes. The user stays in the chat. The conversational engine stays in the supervisor lane. The executor stays in the execution lane. These roles do not blur. Once delegation begins, the user does not open the terminal.

For server deployment, the local MCP server is replaced by an SSH-capable MCP, and the executor runs on the remote server under a dedicated low-privilege Unix user. Everything else is identical.

The first sprint executed with the method will be messier than expected. The third sprint will be smoother. By the tenth sprint, the prompt engineering practice will have stabilized, the allocation matrix will reflect measured patterns rather than guesses, and the CLAUDE.md will encode the operational lessons that previous sprints surfaced. The infrastructure pays back from the first session. The disciplines compound over weeks.

Conclusion

The Hybrid Method is, in the simplest formulation, a way of using two existing Claude products together so that they cover complementary regimes of work: a conversational engine for decisions, context, and supervision; an executor engine for sustained autonomous engineering. The user interacts only with the conversational engine. The executor is a subcontractor whose work is supervised through artifacts the user does not need to read.

The infrastructure is small. Three components — a chat subscription, a local MCP server, Claude Code installed — and a shared memory file at the root of the project. The infrastructure does not require a cloud service, a custom protocol, or specialized engineering work.

What requires craft is the operational discipline that turns the infrastructure into a productive system: an allocation policy that engages the right engine for the right kind of work, a prompt engineering practice that translates conversational intent into structured executor instructions, and the simple but consistent separation of lanes that keeps each engine doing what it does well.

What the method offers, in return, is a meaningful shift in what a single person can produce with AI assistance. For a senior engineer, attention is freed for the work only the engineer can do — judgment, validation, scope, real-world correctness — while execution moves to a supervised executor. For a user without an engineering background, the bar to participation is lowered: the prompt-craft expertise that previously gated access to Claude Code’s autonomous mode is provided by the conversational engine on the user’s behalf, allowing the user to contribute domain knowledge and judgment without first acquiring engineering rigor as a prerequisite.

I have used this method daily since April 21, 2026 on projects in production. After a month of measured use, I consider it the most useful AI-assisted engineering organization I have encountered. The throughput gain over single-agent use is approximately an order of magnitude; the output quality is higher; the cognitive cost of context-switching between strategy and execution drops to near zero because the user never leaves the conversational interface. These claims are made carefully and with measurement behind them, not as marketing.

The deeper observation, beyond the immediate productivity gains, is that the Hybrid Method makes implementable in practice a division of labor that has been theoretically advocated for some time: the human decides and validates, the AI executes. Single-agent AI coding tools have come close to this division but have not quite delivered it, because the human has had to remain present in the execution loop to supervise. By dedicating one Claude to supervision and another to execution, with the human attached only to the supervisor, the Hybrid Method delivers the division in a form that holds up under the duration and complexity of real software projects.

The pieces of this assembly are all public. The combination, as far as my search of the public literature on May 20, 2026 indicates, is not yet documented elsewhere. The choice to write this article down was motivated by that absence and by a sense that the window during which writing it down is the most useful contribution available is narrow: the major AI vendors are visibly moving toward native versions of related capabilities, and a built-in feature in Claude.ai that delegates to Claude Code from the chat is, in my reading, plausible within twelve months. Until that arrives, the user-side configuration described here is available to anyone who wants to assemble it.

If the description above is useful, the most valuable next step for a reader is not further reading but practice. Install the four ingredients, write a first version of the CLAUDE.md, attempt a small sprint under the supervision pattern, measure the outcome, refine the allocation matrix and the prompt format on the basis of what was learned. The first sprint will be uneven. The fifth will not. The framework described here is the scaffolding; the productive use of it is built through iteration on real work.

Feedback, prior art that contradicts the claim of originality, refinements to the allocation matrix, and accounts of other implementations are all welcome. The method is named, but it is not finished. Improvements in any of these dimensions are improvements to the shared practice rather than to any individual implementation, and they are how the method becomes useful beyond the small set of practitioners who have so far converged on something resembling it.

— Driss Amiroune

LLMs Are Probabilistic. Your Workflow Shouldn’t Be.

Most AI app demos make the same mistake:

they treat the model like the application.

Prompt in, answer out, maybe a few tool calls, then everybody acts surprised when the thing becomes weird in production.

The problem is not that the model is useless.

The problem is that we keep asking a probabilistic component to behave like deterministic workflow code.

That is the wrong boundary.

The better pattern is simpler:

  • let the model interpret
  • let software validate
  • let the workflow own state
  • let high-risk actions require approval

If you remember nothing else, remember this:

Do not let the model own irreversible state transitions.

Why This Matters

The reliability problem is not hypothetical anymore.

Stanford HAI’s 2026 AI Index reports that in a new accuracy benchmark, hallucination rates across 26 top models ranged from 22% to 94%. The same report says documented AI incidents rose from 233 in 2024 to 362 in 2025.

And yet adoption keeps rising. In the 2026 AI Index economy chapter, 88% of surveyed organizations reported using AI in at least one business function in 2025, and 79% reported regular generative-AI use in at least one function.

So yes, teams are shipping this stuff.

But the same chapter also says scaled AI-agent use stayed in the single digits across nearly all business functions.

That makes sense. People want the upside of LLMs without letting them quietly become the database, the rules engine, and the compliance department at the same time.

Even the Model Vendors Are Telling You This

Anthropic’s “Building Effective Agents” says the most successful implementations they saw used simple, composable patterns, and draws a clear line between:

  • workflows, where LLMs and tools are orchestrated through predefined code paths
  • agents, where models dynamically direct their own processes and tool usage

They also recommend starting with the simplest solution possible and only adding complexity when needed.

OpenAI says something similar in a different way. In the Structured Outputs launch, they explicitly note that model behavior is inherently non-deterministic, and that better model performance alone still did not meet the reliability developers need for robust applications. Their answer was not “prompt harder.” It was deterministic constrained decoding around model output.

That is the pattern.

Use the model where probability is acceptable.
Use deterministic engineering where correctness has to be enforced.

The Architectural Rule

Here is the rule I trust:

LLMs should propose actions. Software should decide whether those actions are allowed.

That means the model is great for:

  • intent extraction
  • document understanding
  • summarization
  • classification
  • drafting
  • tool selection in bounded contexts

And it should usually not be the final authority for:

  • permissions
  • pricing
  • payment execution
  • account state
  • refunds
  • contract interpretation
  • compliance gates
  • destructive writes

If the model says “refund this order,” that is not a refund. That is a recommendation.

Your application should still check:

  • does the order exist?
  • is it refundable?
  • is the amount within policy?
  • does the caller have permission?
  • is there already a refund in flight?
  • does this require human approval?

That logic belongs in code, not in hope.

The Boring Architecture That Wins

This is the stack I would trust in production.

1. LLM as interpreter

The model turns unstructured input into a candidate action:

{
  "intent": "issue_refund",
  "order_id": "ord_123",
  "reason": "duplicate_charge",
  "amount": 49.0
}

2. Typed output contract

Do not parse vibes. Parse a schema.

OpenAI’s Structured Outputs guide exists for a reason: even when model quality improves, you still need deterministic enforcement around output shape.

3. Deterministic validator

Now your real application runs checks:

  • schema validation
  • authz
  • business rules
  • idempotency
  • resource existence
  • threshold checks
  • rate limits

4. Workflow engine or state machine

The model does not own the state transition. Your workflow does.

For example:

requested -> validated -> approved -> executed -> recorded

If validation fails, the workflow branches. If approval is required, it pauses. If execution fails, it retries or dead-letters.

5. Scoped tools

Tools should be narrow, explicit, and permissioned.

OpenAI’s practical guide to building agents recommends assessing tool risk based on things like write access, reversibility, and financial impact, then pausing or escalating to a human for high-risk functions.

That is not compliance theater. That is basic architecture.

6. Tracing, logs, and evals

If you cannot inspect which prompt, tool result, validation step, and branch decision led to an action, you are not debugging a system. You are guessing.

The Anti-Pattern

Here is the version I do not trust:

  1. User message goes straight to agent.
  2. Agent decides what the business rule probably is.
  3. Agent writes to the database.
  4. Agent sends the email.
  5. Agent updates the CRM.
  6. Everybody hopes the prompt was good enough.

This looks fast in a demo because all the complexity is hidden.

It breaks in production because all the accountability is hidden too.

When something goes wrong, you will not know whether the failure came from:

  • bad retrieval
  • bad prompt assumptions
  • stale tool data
  • missing permission checks
  • race conditions
  • duplicate writes
  • a plain old hallucination

And worse, you may not know until after money moved or customers were contacted.

NIST Is Basically Handing You the Blueprint

NIST’s AI Risk Management Framework is not just policy paperwork. It is practical engineering guidance if you read it that way.

Some of the most useful parts for builders:

  • define and differentiate human and AI roles
  • document knowledge limits and how outputs will be overseen
  • test systems before deployment and regularly in production
  • monitor functionality and behavior in production
  • make sure systems can fail safely beyond their knowledge limits
  • document risks, controls, and third-party dependencies

That is just good software engineering with a better vocabulary.

NIST’s Generative AI Profile goes further and says generative AI may require additional human review, tracking, documentation, and management oversight.

Which is exactly what experienced teams discover after the first few production incidents anyway.

A Good Mental Model

Think about the LLM as a perception layer, not a transaction layer.

It helps you turn messy human input into structured candidates.

It does not get to redefine the core invariants of your system.

So instead of this:

model -> action

Build this:

model -> proposal -> validation -> policy check -> approval (if needed) -> execution -> audit log

That extra machinery is not bureaucracy.

It is the difference between an AI feature and an AI system you can trust.

What I Would Build First

If I were building an AI workflow today, I would start in this order:

  1. One narrow use case with real pain.
  2. One model call that returns typed output.
  3. One validator layer with explicit business rules.
  4. One small set of tools with clear permissions.
  5. One approval path for high-risk actions.
  6. One trace per run.
  7. One eval set based on real failures, not synthetic optimism.

Anthropic’s guidance to start simple is right. OpenAI’s guardrail guidance is right. The teams getting burned are usually the ones skipping the boring layers because the model looked good in staging.

The Take

AI does not remove the need for software architecture.

It raises the price of getting architecture wrong.

LLMs are powerful because they handle ambiguity well. They are dangerous when you let that ambiguity leak into the parts of your system that are supposed to be exact.

So let the model read.
Let the model classify.
Let the model draft.
Let the model suggest.

But let deterministic systems own:

  • truth
  • policy
  • permissions
  • state transitions
  • side effects

AI makes mistakes.

That is not a reason to avoid building with it.

It is a reason to build the part around it like an engineer.

Sources

Deploying Tempo Distributed Tracing Backend on Ubuntu 24.04

Tempo is an open-source, high-scale distributed tracing backend from Grafana Labs that ingests trace spans over Jaeger, OpenTelemetry, and Zipkin protocols. It requires only object storage and pairs natively with Grafana for trace visualization. This guide deploys Tempo using Docker Compose with Traefik handling automatic HTTPS, and verifies the backend through its status endpoints. By the end, you’ll have Tempo accepting trace spans over HTTPS at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/tempo-tracing/{tempo-data,tempo-config}
$ cd ~/tempo-tracing

2. Set ownership for the Tempo data directory:

$ sudo chown -R 10001:10001 tempo-data

3. Create the environment file:

$ nano .env
DOMAIN=tempo.example.com
LETSENCRYPT_EMAIL=admin@example.com

4. Create the Tempo configuration file:

$ nano tempo-config/tempo-config.yaml
server:
  http_listen_port: 3200

distributor:
  receivers:
    jaeger:
      protocols:
        thrift_http:
        grpc:
    otlp:
      protocols:
        http:
        grpc:

storage:
  trace:
    backend: local
    local:
      path: /var/tempo/traces
    wal:
      path: /var/tempo/wal

compactor:
  compaction:
    block_retention: 168h

Deploy with Docker Compose

1. Create the Docker Compose manifest:

$ nano docker-compose.yaml
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    restart: unless-stopped

  tempo:
    image: grafana/tempo:latest
    container_name: tempo
    hostname: tempo
    expose:
      - "3200"
      - "4317"
      - "4318"
      - "14250"
      - "14268"
    volumes:
      - "./tempo-config/tempo-config.yaml:/etc/tempo/tempo.yaml"
      - "./tempo-data:/var/tempo"
    command: -config.file=/etc/tempo/tempo.yaml
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.tempo.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.tempo.entrypoints=websecure"
      - "traefik.http.routers.tempo.tls.certresolver=letsencrypt"
      - "traefik.http.services.tempo.loadbalancer.server.port=3200"
    restart: unless-stopped

volumes:
  letsencrypt:

2. Start the services:

$ docker compose up -d

3. Verify the services are running:

$ docker compose ps

4. View the logs:

$ docker compose logs

Access Tempo

1. Check the readiness endpoint:

$ curl https://tempo.example.com/ready

A ready response confirms the backend is up.

2. Query the service status:

$ curl https://tempo.example.com/status

3. Check the build information:

$ curl https://tempo.example.com/api/status/buildinfo
{"version":"v2.9.0","revision":"032d47627","branch":"main","buildUser":"","buildDate":"","goVersion":"go1.25.1"}

Next Steps

Tempo is running and accepting distributed trace spans over HTTPS. From here you can:

  • Add Tempo as a data source in Grafana for trace visualization
  • Point OpenTelemetry, Jaeger, or Zipkin exporters at your domain
  • Swap the local storage backend for S3-compatible object storage at scale

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Technitium DNS Server Platform on Ubuntu 24.04

Technitium DNS Server is an open-source, authoritative and recursive DNS server built for privacy and security, with DNS-over-TLS, DNS-over-HTTPS, ad blocking, and a modern web console for zone and record management. This guide deploys Technitium using Docker Compose with Traefik securing the web console over HTTPS, after freeing the system’s port 53. By the end, you’ll have a working DNS server resolving queries with an HTTPS-secured admin console.

Free Port 53

Ubuntu’s systemd-resolved binds port 53 by default. Release it before deploying.

1. Stop and disable systemd-resolved:

$ sudo systemctl stop systemd-resolved
$ sudo systemctl disable systemd-resolved

2. Replace the resolver configuration:

$ sudo rm /etc/resolv.conf
$ echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/technitium/{config,letsencrypt}
$ cd ~/technitium

2. Create the environment file:

$ nano .env
DOMAIN=technitium.example.com
LETSENCRYPT_EMAIL=admin@example.com

Deploy with Docker Compose

1. Add your user to the Docker group:

$ sudo usermod -aG docker $USER
$ newgrp docker

2. Create the Docker Compose manifest:

$ nano docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik-public"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  dns-server:
    image: technitium/dns-server:latest
    container_name: dns-server
    restart: unless-stopped
    environment:
      - TZ=UTC
      - DNS_SERVER_DOMAIN=${DOMAIN}
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    volumes:
      - ./config:/etc/dns
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dns.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.dns.entrypoints=websecure"
      - "traefik.http.routers.dns.tls=true"
      - "traefik.http.routers.dns.tls.certresolver=le"
      - "traefik.http.services.dns.loadbalancer.server.port=5380"

3. Start the services:

$ docker compose up -d

4. Verify the services are running:

$ docker compose ps

Initial Configuration

  1. Navigate to https://technitium.example.com.
  2. Log in with the default credentials admin / admin.
  3. Change the password when prompted.
  4. Go to Settings → Proxy & Forwarders.
  5. Add upstream DNS providers, one per line: 1.1.1.1 and 8.8.8.8.
  6. Click Save Settings.

Test Resolution

From any machine, query the server by its IP:

$ dig @SERVER_IP vultr.com

A valid answer section confirms the DNS server is resolving queries.

Next Steps

Technitium is running with an HTTPS-secured console. From here you can:

  • Create authoritative zones and records for your domains
  • Enable DNS-over-TLS and DNS-over-HTTPS for encrypted resolution
  • Turn on block lists to filter ads and malicious domains network-wide

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Gitea Self-Hosted Git Platform on Ubuntu 24.04

Gitea is a lightweight, self-hosted Git service offering repository hosting, code review, issue tracking, and built-in CI/CD. It runs from a single binary backed by a database and scales comfortably on modest hardware. This guide deploys Gitea with a MariaDB backend using Docker Compose, with Traefik handling automatic HTTPS. By the end, you’ll have a fully functional Gitea instance served securely at your domain.

Set Up the Directory Structure

1. Create the project directory structure:

$ mkdir -p ~/gitea/{data,db,letsencrypt}
$ cd ~/gitea

2. Create the environment file:

$ nano .env
GITEA_DB_USER=gitea
GITEA_DB_PASS=STRONG_APP_PASSWORD
MYSQL_ROOT_PASS=STRONG_MYSQL_ROOT_PASSWORD
GITEA_DB_NAME=giteadb
GITEA_DOMAIN=gitea.example.com
LETSENCRYPT_EMAIL=admin@example.com

Deploy with Docker Compose

1. Add your user to the Docker group:

$ sudo usermod -aG docker $USER
$ newgrp docker

2. Create the Docker Compose manifest:

$ nano docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  db:
    image: mariadb:latest
    container_name: gitea-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASS}
      - MYSQL_DATABASE=${GITEA_DB_NAME}
      - MYSQL_USER=${GITEA_DB_USER}
      - MYSQL_PASSWORD=${GITEA_DB_PASS}
    volumes:
      - ./db:/var/lib/mysql

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    depends_on:
      - db
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=mysql
      - DB_HOST=db:3306
      - DB_NAME=${GITEA_DB_NAME}
      - DB_USER=${GITEA_DB_USER}
      - DB_PASSWD=${GITEA_DB_PASS}
      - ROOT_URL=https://${GITEA_DOMAIN}
    volumes:
      - ./data:/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.gitea.rule=Host(`${GITEA_DOMAIN}`)"
      - "traefik.http.routers.gitea.entrypoints=websecure"
      - "traefik.http.routers.gitea.tls=true"
      - "traefik.http.routers.gitea.tls.certresolver=le"
      - "traefik.http.services.gitea.loadbalancer.server.port=3000"

3. Start the services:

$ docker compose up -d

4. Verify the services are running:

$ docker compose ps

Complete the Installation

  1. Open https://gitea.example.com in a browser to load the initial configuration page.
  2. Leave the database settings at their defaults — they match the Compose environment.
  3. Configure the Administrator Account Settings with your admin username, password, and email.
  4. Click Install Gitea to finalize the setup.

Next Steps

Gitea is running and served securely over HTTPS. From here you can:

  • Create organizations and repositories, and invite collaborators
  • Enable Gitea Actions for built-in CI/CD pipelines
  • Configure SMTP for email notifications and SSH for Git over SSH

For the full guide with additional tips, visit the original article on Vultr Docs.

Deploying Vault Secret and Key Management on Ubuntu 24.04

HashiCorp Vault is an open-source tool for managing secrets, encryption keys, and sensitive credentials with fine-grained access control and audit logging. This guide deploys Vault using Docker Compose with the Raft integrated storage backend and Traefik handling automatic HTTPS, then initializes and unseals the cluster. By the end, you’ll have a production-grade Vault accessible securely at your domain.

Set Up the Directory Structure and Configuration

1. Create the project directory structure:

$ mkdir -p ~/vault/{config,data,logs,letsencrypt}

2. Set ownership for the data and log directories:

$ sudo chown -R 100:100 ~/vault/data ~/vault/logs
$ cd ~/vault

3. Create the Vault configuration file:

$ nano config/vault.hcl
ui = true

storage "raft" {
  path    = "/vault/data"
  node_id = "node1"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = "true"
}

api_addr = "https://vault.example.com"
cluster_addr = "http://vault:8201"
disable_mlock = false

4. Create the environment file:

$ nano .env
VAULT_DOMAIN=vault.example.com
LETSENCRYPT_EMAIL=[email protected]

Deploy with Docker Compose

1. Add your user to the Docker group:

$ sudo usermod -aG docker $USER
$ newgrp docker

2. Create the Docker Compose manifest:

$ nano docker-compose.yml
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  vault:
    image: hashicorp/vault:latest
    container_name: vault
    restart: unless-stopped
    cap_add:
      - IPC_LOCK
    ports:
      - "8200:8200"
    volumes:
      - ./config:/vault/config
      - ./data:/vault/data
      - ./logs:/vault/logs
    environment:
      VAULT_ADDR: "http://127.0.0.1:8200"
      VAULT_API_ADDR: "http://127.0.0.1:8200"
    command: server
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vault.rule=Host(`${VAULT_DOMAIN}`)"
      - "traefik.http.routers.vault.entrypoints=websecure"
      - "traefik.http.routers.vault.tls=true"
      - "traefik.http.routers.vault.tls.certresolver=le"
      - "traefik.http.services.vault.loadbalancer.server.port=8200"

3. Start the services:

$ docker compose up -d

4. Verify the services are running:

$ docker compose ps

Initialize and Unseal Vault

1. Initialize the Vault cluster:

$ docker exec -it vault vault operator init

Save the five unseal keys and the initial root token from the output in a secure location — they cannot be recovered.

2. Open the UI:

Navigate to https://vault.example.com in a browser.

3. Unseal the vault:

Enter any 3 of the 5 unseal keys, then authenticate with the initial root token.

Next Steps

Vault is running, initialized, and unsealed over HTTPS. From here you can:

  • Enable secrets engines such as KV, database, or PKI
  • Configure auth methods like AppRole, OIDC, or tokens for applications
  • Set up audit devices and automate unsealing with auto-unseal

For the full guide with additional tips, visit the original article on Vultr Docs.