Back to Blog
AI & Agents

Choosing an Agent Framework in 2026: LangGraph vs CrewAI vs Roll-Your-Own

A field comparison of the frameworks I've actually shipped on — explicit graphs vs role-based crews vs hand-rolled loops — and how to tell which one your project really needs.

Amit ShrivastavaJuly 8, 20269 min read

A code snippet from this post was tested

Node.js v22.23.1 · Verified July 8, 2026

Logic from this post, adapted into a runnable form and executed by the publishing pipeline.

node verify.mjs

Snippet

class OpenAI {
  constructor({ apiKey }) {
    this.apiKey = apiKey;
  }

  chat = {
    completions: {
      create: async ({ model, messages, temperature, max_tokens }) => {
        const userMessage = messages.find(msg => msg.role === "user").content;

        if (userMessage.includes("Classify the following email")) {
          if (userMessage.includes("I'd like to know more about your new product")) {
            return { choices: [{ message: { content: "Sales" } }] };
          }
          if (userMessage.includes("My order hasn't arrived yet")) {
            return { choices: [{ message: { content: "Support" } }] };
          }
          if (userMessage.includes("Just wanted to say hi")) {
            return { choices: [{ message: { content: "General Inquiry" } }] };
          }
          if (userMessage.includes("Free money now!")) {
            return { choices: [{ message: { content: "Spam" } }] };
          }
        }

        if (userMessage.includes("Draft a polite sales follow-up reply")) {
          return { choices: [{ message: { content: "Thank you for your interest! We'd love to share more about our new product. When is a good time to connect?" } }] };
        }
        if (userMessage.includes("Draft a support reply for this email")) {
             return { choices: [{ message: { content: "We apologize for the delay. Please provide your order number so we can investigate." } }] };
        }

        return { choices: [{ message: { content: "Default LLM response." } }] };
      },
    },
  };
}

const openai = new OpenAI({ apiKey: "sk-mock-api-key" });

async function processEmail(emailContent) {
  // Step 1: Classify email
  const classificationPrompt = `Classify the following email into one of these categories: Sales, Support, General Inquiry, Spam. Email: "${emailContent}"`;
  const classificationResponse = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [{ role: "user", content: classificationPrompt }],
    temperature: 0.1,
    max_tokens: 50
  });
  const category = classificationResponse.choices[0].message.content?.trim();

  // Step 2: Suggest reply based on classification
  let replySuggestion = null;
  if (category === "Sales") {
    const replyPrompt = `Draft a polite sales follow-up reply for this email: "${emailContent}"`;
    const replyResponse = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: replyPrompt }],
      temperature: 0.7,
      max_tokens: 200
    });
    replySuggestion = replyResponse.choices[0].message.content;
  } else if (category === "Support") {
    const replyPrompt = `Draft a support reply for this email: "${emailContent}"`;
    const replyResponse = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: replyPrompt }],
      temperature: 0.7,
      max_tokens: 200
    });
    replySuggestion = replyResponse.choices[0].message.content;
  }
  // Other categories might just get logged or forwarded

  return { category, replySuggestion };
}

async function runTests() {
  console.log("--- Test Case 1: Sales Email ---");
  const salesEmail = "I'd like to know more about your new product, could you send me details?";
  const salesResult = await processEmail(salesEmail);
  console.log("Email:", salesEmail);
  console.log("Processed:", JSON.stringify(salesResult, null, 2));

  console.log("\n--- Test Case 2: Support Email ---");
  const supportEmail = "My order hasn't arrived yet, can you check the status?";
  const supportResult = await processEmail(supportEmail);
  console.log("Email:", supportEmail);
  console.log("Processed:", JSON.stringify(supportResult, null, 2));

  console.log("\n--- Test Case 3: Spam Email ---");
  const spamEmail = "Free money now! Click this link to claim your prize.";
  const spamResult = await processEmail(spamEmail);
  console.log("Email:", spamEmail);
  console.log("Processed:", JSON.stringify(spamResult, null, 2));

  console.log("\n--- Test Case 4: General Inquiry Email ---");
  const generalEmail = "Just wanted to say hi and hope you're having a good week.";
  const generalResult = await processEmail(generalEmail);
  console.log("Email:", generalEmail);
  console.log("Processed:", JSON.stringify(generalResult, null, 2));
}

runTests();

Captured output

--- Test Case 1: Sales Email ---
Email: I'd like to know more about your new product, could you send me details?
Processed: {
  "category": "Sales",
  "replySuggestion": "Thank you for your interest! We'd love to share more about our new product. When is a good time to connect?"
}

--- Test Case 2: Support Email ---
Email: My order hasn't arrived yet, can you check the status?
Processed: {
  "category": "Support",
  "replySuggestion": "We apologize for the delay. Please provide your order number so we can investigate."
}

--- Test Case 3: Spam Email ---
Email: Free money now! Click this link to claim your prize.
Processed: {
  "category": "Spam",
  "replySuggestion": null
}

--- Test Case 4: General Inquiry Email ---
Email: Just wanted to say hi and hope you're having a good week.
Processed: {
  "category": "General Inquiry",
  "replySuggestion": null
}

Choosing an Agent Framework in 2026: LangGraph vs CrewAI vs Roll-Your-Own

The agentic AI landscape is evolving at a breakneck pace. Just a couple of years ago, "agents" were mostly theoretical. Now, in 2026, I'm actively building and shipping them. As a Senior Software Engineer who's dipped his toes in frontend, Web3, and now deep into AI, I've had the opportunity to get my hands dirty with several approaches. When it comes to building complex, multi-step AI agents, the fundamental question I always ask myself (and often get asked) is: LangGraph, CrewAI, or just roll my own solution?

This isn't an academic comparison. This is about what works, what breaks, and what ships when you're under pressure. I've used all three approaches on real projects, and each has its sweet spot. Let's dig in.

The Explicit Graph: LangGraph

LangGraph, built on top of LangChain, is my go-to for projects requiring precise, stateful, and often circular agentic workflows. Think of it as a finite state machine for your AI agents. You define nodes (actions, LLM calls, tool uses) and edges (transitions between nodes based on conditions or output).

Why I choose LangGraph:

  • Transparency and Debuggability: This is its superpower. When an agent workflow goes off the rails, tracing the execution path through a clearly defined graph is invaluable. Each step's input and output are explicit.
  • Complex Looping and State: For scenarios needing human-in-the-loop, iterative refinement, or self-correction loops, LangGraph shines. Its state management allows agents to remember context across multiple turns.
  • Fine-grained Control: You dictate every transition. This is crucial for applications where reliability and predictable behavior are paramount, like automated code generation or complex data analysis pipelines.

Where LangGraph can be a stretch:

  • Initial Boilerplate: Setting up the graph with all its nodes and edges can feel like a lot of code initially, especially for simpler tasks.
  • Steep Learning Curve: While powerful, understanding state, checkpoints, and conditional edges takes a bit of time, especially if you're new to state machines.

Example Use Case (TypeScript): Imagine a self-correcting agent that writes a blog post, then reviews it, and iteratively refines it until it meets criteria.

// Simplified LangGraph node definitions (pseudo-code)
import { StateGraph, END } from "@langchain/langgraph";

type AgentState = {
  draft: string;
  reviewResult: string;
  revisionCount: number;
};

const graph = new StateGraph<AgentState>();

graph.addNode("write_draft", async (state) => {
  // Call LLM to generate initial draft
  return { draft: "Initial blog post draft...", reviewResult: "" };
});

graph.addNode("review_draft", async (state) => {
  // Call LLM to review the draft and provide feedback
  const feedback = await llm.invoke(`Review this draft: ${state.draft}`);
  return { reviewResult: feedback };
});

graph.addNode("revise_draft", async (state) => {
  // Call LLM to revise the draft based on feedback
  const revisedDraft = await llm.invoke(`Revise this: ${state.draft} considering: ${state.reviewResult}`);
  return { draft: revisedDraft, revisionCount: state.revisionCount + 1, reviewResult: "" };
});

graph.addEdge("write_draft", "review_draft");
graph.addEdge("review_draft", "revise_draft"); // Always revise after review

graph.addConditionalEdges(
  "revise_draft",
  (state) => {
    // Check if review suggests more revisions or if max revisions reached
    if (state.reviewResult.includes("needs more work") && state.revisionCount < 3) {
      return "review_draft"; // Loop back for another review
    }
    return END; // Otherwise, we're done
  }
);
graph.setEntryPoint("write_draft");

// Compile and run the graph...

The Role-Based Crew: CrewAI

CrewAI burst onto the scene with a fresh perspective: AI agents working together like a human team, each with a defined role, goal, and set of tools. This framework excels at orchestrating collaborative tasks where agents need to hand off work and build upon each other's contributions.

Why I choose CrewAI:

  • Intuitive Paradigm: The "crew" analogy is incredibly easy to grasp and explain. It maps well to real-world teamwork.
  • Rapid Prototyping: For many common multi-agent patterns (research, analysis, content creation), CrewAI can get you up and running faster than LangGraph.
  • Emergent Behavior: By giving agents clear roles and goals, you often get surprisingly sophisticated emergent behavior as they figure out how to collaborate.
  • Out-of-the-box Task/Agent Definition: Its structured way of defining agents (role, goal, backstory) and tasks (description, expected output, agent to assign) simplifies setup.

Where CrewAI can be a stretch:

  • Less Granular Control: While roles and tasks are clear, the exact sequence of internal thought and action within a crew can be less transparent than a LangGraph. Debugging can sometimes feel like debugging a black box if agents get stuck.
  • State Management: State management is handled through task outputs and agent memory. It's less explicit than LangGraph's shared state object, which can be a double-edged sword: simpler for basic flows, harder for complex, circular dependencies.
  • Performance: Depending on the complexity and number of agents, the overhead of orchestrating multiple LLM calls can sometimes be higher.

Example Use Case (TypeScript/Pseudo-code): A marketing crew researching a topic and writing a tweet thread.

// Simplified CrewAI agents and tasks (pseudo-code)
import { Agent, Task, Crew } from "@crewai/crew"; // Assuming a TS client eventually

const researcher = new Agent({
  role: "Senior Research Analyst",
  goal: "Identify key trends and data points related to {topic}",
  backstory: "Expert in deep market research and data synthesis.",
  tools: [webSearchTool, dataAnalysisTool],
  verbose: true
});

const contentCreator = new Agent({
  role: "Social Media Strategist",
  goal: "Craft engaging tweet threads from research findings",
  backstory: "Master of concise and impactful social media content.",
  tools: [twitterDraftTool],
  verbose: true
});

const researchTask = new Task({
  description: "Conduct comprehensive research on the latest {topic} innovations.",
  expectedOutput: "A detailed report summarizing key findings, trends, and competitor analysis.",
  agent: researcher,
  verbose: true
});

const tweetTask = new Task({
  description: "Generate a 5-tweet thread about the research findings, focusing on engagement.",
  expectedOutput: "A 5-part tweet thread, ready for publishing, including hashtags.",
  agent: contentCreator,
  context: [researchTask], // Output of researchTask becomes context for tweetTask
  verbose: true
});

const marketingCrew = new Crew({
  agents: [researcher, contentCreator],
  tasks: [researchTask, tweetTask],
  verbose: true
});

// marketingCrew.kickoff({ topic: "AI in Healthcare" });

The "Roll-Your-Own" Approach

Before these frameworks matured, rolling your own agent loops was the only way. And honestly, it still is for many projects. This typically involves custom Python or TypeScript code orchestrating LLM calls, tool usage, and state management within a simple while loop or sequence of functions.

Why I choose Roll-My-Own:

  • Ultimate Flexibility: No framework constraints. You build exactly what you need, nothing more, nothing less. Ideal for highly specialized or performance-critical flows.
  • Minimal Overhead: Fewer dependencies, less magic. If your agent logic is simple, a custom loop is often more performant and easier to maintain.
  • Deep Integration: When your agent needs to interact deeply with an existing complex codebase or proprietary systems, a custom solution can be easier to embed.

Where Roll-My-Own can be a stretch:

  • Reinventing the Wheel: For common agentic patterns (like tool use, memory management, multi-turn conversations), you'll end up writing code that LangChain or CrewAI already provide.
  • Debugging Complexity: Orchestrating complex state and agent interactions without a visual or structured framework can quickly become a spaghetti of if/else statements and hard-to-trace logic.
  • Scalability Challenges: As your agent's capabilities grow, managing its state, tool dispatch, and error handling manually can become a significant burden.

Example Use Case (TypeScript): A simple, single-turn agent that classifies an email and suggests a reply.

// Basic email classification and reply suggestion
import { OpenAI } from "openai"; // Or any LLM client

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function processEmail(emailContent: string) {
  // Step 1: Classify email
  const classificationPrompt = `Classify the following email into one of these categories: Sales, Support, General Inquiry, Spam. Email: "${emailContent}"`;
  const classificationResponse = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [{ role: "user", content: classificationPrompt }],
    temperature: 0.1,
    max_tokens: 50
  });
  const category = classificationResponse.choices[0].message.content?.trim();

  // Step 2: Suggest reply based on classification
  let replySuggestion: string | null = null;
  if (category === "Sales") {
    const replyPrompt = `Draft a polite sales follow-up reply for this email: "${emailContent}"`;
    const replyResponse = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: replyPrompt }],
      temperature: 0.7,
      max_tokens: 200
    });
    replySuggestion = replyResponse.choices[0].message.content;
  } else if (category === "Support") {
    // ... similar logic for support
  }
  // Other categories might just get logged or forwarded

  return { category, replySuggestion };
}

// processEmail("I'd like to know more about your new product...");

When to Use Which: A Decision Flowchart

To formalize my thought process, here's a quick decision tree:

graph TD
    A[Start: What are you building?] --> B{Does it require precise, stateful looping and explicit transitions?};
    B -- Yes --> C[LangGraph];
    B -- No --> D{Does it involve agents collaborating with distinct roles and knowledge?};
    D -- Yes --> E[CrewAI];
    D -- No --> F{Is the workflow very simple, sequential, and does not require complex shared state?};
    F -- Yes --> G[Roll-Your-Own];
    F -- No --> H[Re-evaluate complexity, perhaps a mix or revisit LangGraph/CrewAI for more structure];

My Personal Verdict

For most complex agentic applications I'm building today, LangGraph has become my default. The ability to visualize, debug, and precisely control state transitions gives me immense confidence in production. When I need to really trust the agent's flow and potentially integrate human oversight, LangGraph's explicit nature is unparalleled.

However, for rapid prototyping, internal tools, or scenarios where the "team collaboration" metaphor strongly fits, CrewAI is a fantastic choice. It gets you productive quickly and often inspires clever emergent behaviors.

And of course, for the truly simple, single-shot, or deeply embedded use cases, rolling your own custom logic remains viable and often superior. Don't over-engineer with a framework if a few await llm.invoke() calls do the trick.

The key takeaway for 2026 is that agentic development is all about making thoughtful architectural choices. There's no one-size-fits-all, but by understanding the strengths and weaknesses of these frameworks (and the "no-framework" framework), you can build robust, reliable, and innovative AI solutions that actually ship.


Connect with me!

I'm always keen to hear about your experiences and challenges in the AI agent space. Feel free to connect with me on LinkedIn or X—let's discuss what you're building!

Agent Frameworks
LangGraph
CrewAI
Architecture