Skip to content

When Should You Use LangChain or Semantic Kernel vs Simple File-Based AI Agent Approaches?

Problem

I spent three weeks learning LangChain for a simple personal automation project. The result? Over 500 lines of code, confusing abstractions, and an agent that was harder to debug than my original bash script.

The mistake wasn’t LangChain itself. The mistake was using a production-grade framework for a personal productivity tool.

A Reddit discussion in r/AI_Agents crystallized this insight:

“It depends on the usecase. For your own productivity yes. But for creating production level software you need code.” — mohdgame

“Simple markdown and folders just work and more importantly more people can grok it than langchain and python” — IversusAI

I had chosen the wrong tool for my use case. Let me share what I learned about when to use each approach.

The Tension

When building AI agents, you face a fundamental architecture decision:

framework-comparison.txt
┌─────────────────────────────────────────────────────────────────────┐
│ FRAMEWORK VS FILE-BASED │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ LangChain/Semantic Kernel File-Based (Markdown/Folders) │
│ ───────────────────────── ────────────────────────────── │
│ │
│ + Built-in observability + Maximum transparency │
│ + Memory management + Easy debugging │
│ + Tool orchestration + Version control friendly │
│ + Multi-model support + Non-technical accessible │
│ + Error handling patterns + Low learning curve │
│ + Production scaling + Fast prototyping │
│ │
│ - Steep learning curve - Manual state management │
│ - Abstraction complexity - Limited built-in features │
│ - Debugging through layers - Custom error handling │
│ - Team expertise required - Scale challenges │
│ │
└─────────────────────────────────────────────────────────────────────┘

The key insight: Neither approach is better. They serve different purposes.

When I Tried Both Approaches

Let me show you the difference through my actual project. I wanted to build an agent that reads my daily notes and generates a weekly summary.

My File-Based Attempt

I started with a simple folder structure:

project-structure.txt
my-agent/
├── contexts/
│ ├── monday.md
│ ├── tuesday.md
│ └── wednesday.md
├── outputs/
│ └── weekly-summary.md
└── agent.py

The implementation was straightforward:

simple_agent.py
from pathlib import Path
from anthropic import Anthropic
class FileAgent:
def __init__(self, workspace: str):
self.workspace = Path(workspace)
self.contexts = self.workspace / "contexts"
self.outputs = self.workspace / "outputs"
self.client = Anthropic()
def read_contexts(self) -> str:
"""Read all markdown files from contexts folder."""
all_context = []
for file in sorted(self.contexts.glob("*.md")):
all_context.append(f"=== {file.stem} ===\n{file.read_text()}")
return "\n\n".join(all_context)
def generate_summary(self) -> str:
"""Generate weekly summary from contexts."""
context = self.read_contexts()
response = self.client.messages.create(
model="claude-opus-4-20250514",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"Summarize my week based on these notes:\n\n{context}"
}]
)
summary = response.content[0].text
output_file = self.outputs / "weekly-summary.md"
output_file.write_text(summary)
return summary
# Usage
agent = FileAgent("./my-agent")
summary = agent.generate_summary()
print(summary)

Total: 35 lines of code. I could understand every line. When something broke, I knew exactly where to look.

My LangChain Attempt

Then I thought, “I should make this production-ready.” So I rebuilt it with LangChain:

langchain_agent.py
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.memory import ConversationBufferMemory
from langchain.tools import Tool
from langchain_openai import ChatOpenAI
from langchain.callbacks import tracing_v2
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter
from pydantic import BaseModel, Field
import logging
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ContextInput(BaseModel):
directory: str = Field(description="Directory containing markdown files")
class SummaryInput(BaseModel):
contexts: str = Field(description="Combined context from files")
class ProductionAgent:
def __init__(self, model_name: str = "gpt-4"):
self.llm = ChatOpenAI(model=model_name, temperature=0)
self.memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
self.tools = self._setup_tools()
self.agent = self._create_agent()
self.logger = logger
def _setup_tools(self) -> list[Tool]:
def load_contexts(directory: str) -> str:
loader = DirectoryLoader(directory, glob="**/*.md")
docs = loader.load()
splitter = MarkdownHeaderTextSplitter()
split_docs = [splitter.split_text(doc.page_content) for doc in docs]
return "\n".join([str(doc) for sublist in split_docs for doc in sublist])
def generate_summary(contexts: str) -> str:
return self.llm.invoke(
f"Summarize the following weekly notes:\n\n{contexts}"
).content
return [
Tool(
name="load_contexts",
func=load_contexts,
description="Load all markdown files from a directory",
args_schema=ContextInput
),
Tool(
name="generate_summary",
func=generate_summary,
description="Generate a summary from combined contexts",
args_schema=SummaryInput
)
]
def _create_agent(self) -> AgentExecutor:
agent = create_openai_functions_agent(
llm=self.llm,
tools=self.tools,
prompt=self._create_prompt()
)
return AgentExecutor(
agent=agent,
tools=self.tools,
memory=self.memory,
verbose=True,
max_iterations=5,
callbacks=[tracing_v2.LangChainTracer()]
)
def _create_prompt(self):
from langchain.prompts import ChatPromptTemplate
return ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant that summarizes weekly notes."),
("user", "{input}"),
("assistant", "{agent_scratchpad}")
])
async def process(self, directory: str) -> str:
try:
result = await self.agent.ainvoke({
"input": f"Load contexts from {directory} and generate a summary"
})
return result["output"]
except Exception as e:
self.logger.error(f"Agent failed: {e}")
raise
# Usage
import asyncio
agent = ProductionAgent()
summary = asyncio.run(agent.process("./my-agent/contexts"))
print(summary)

Total: 95 lines of code. More abstractions, more complexity.

What Went Wrong

The problem wasn’t LangChain. The problem was my use case.

My weekly summary agent has these characteristics:

  • Single user (me)
  • Runs once per week
  • Linear workflow (read files, generate summary)
  • No complex state management
  • No multi-agent coordination
  • No enterprise integrations

For this use case, the file-based approach is superior because:

  1. Transparency: I can read any .md file and understand the context
  2. Debugging: When the summary is wrong, I check the source files
  3. Version control: Git shows exactly what changed
  4. No learning curve: Anyone can understand the folder structure

Decision Framework

After this experience, I created a decision framework for choosing between approaches.

Choose LangChain or Semantic Kernel When:

framework-checklist.txt
PRODUCTION REQUIREMENTS
========================
[ ] Multi-user system with authentication
[ ] SLA requirements (99.9% uptime)
[ ] Horizontal scaling needed
[ ] Complex state across sessions
ORCHESTRATION COMPLEXITY
========================
[ ] Multi-step reasoning with branching
[ ] Agent-to-agent communication
[ ] Tool calling with retry logic
[ ] Vector database memory systems
INTEGRATION REQUIREMENTS
========================
[ ] Multiple LLM providers (OpenAI, Anthropic, local)
[ ] Enterprise integrations (databases, APIs, queues)
[ ] Observability (tracing, logging, metrics)
[ ] Rate limiting and cost management
TEAM STRUCTURE
==============
[ ] Multiple developers collaborating
[ ] Need standardized patterns
[ ] Code review and quality gates required

If you checked 3 or more boxes, consider a framework.

Choose File-Based When:

file-based-checklist.txt
PERSONAL PRODUCTIVITY
=====================
[ ] Individual developer workflow
[ ] Knowledge management system
[ ] Personal automation scripts
RAPID PROTOTYPING
=================
[ ] Testing ideas quickly
[ ] POC development
[ ] Learning and experimentation
TRANSPARENCY REQUIREMENTS
========================
[ ] Non-technical stakeholders need to understand
[ ] Straightforward debugging needed
[ ] Simple version control critical
SIMPLE WORKFLOWS
================
[ ] Linear processing pipeline
[ ] Single-model interactions
[ ] Minimal state management

If you checked 3 or more boxes, consider file-based.

Real World Example: When I Needed Both

I now use both approaches for different projects.

Personal Project: File-Based

My personal knowledge management system uses markdown files:

personal-km.txt
knowledge/
├── projects/
│ ├── project-a/
│ │ ├── context.md # Project background
│ │ ├── notes.md # Meeting notes
│ │ └── decisions.md # Decision log
│ └── project-b/
│ └── ...
├── templates/
│ ├── daily-standup.md
│ └── weekly-review.md
└── agent.py # Simple 50-line script

My agent script:

knowledge_agent.py
from pathlib import Path
from anthropic import Anthropic
def generate_weekly_review(projects_dir: Path) -> str:
client = Anthropic()
contexts = []
for project_dir in projects_dir.iterdir():
if project_dir.is_dir():
context_file = project_dir / "context.md"
notes_file = project_dir / "notes.md"
if context_file.exists() and notes_file.exists():
contexts.append(
f"PROJECT: {project_dir.name}\n"
f"Context: {context_file.read_text()}\n"
f"Notes: {notes_file.read_text()}"
)
response = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=2000,
messages=[{
"role": "user",
"content": f"Generate a weekly review:\n\n{'-'*40}\n".join(contexts)
}]
)
return response.content[0].text

Why file-based works here: I’m the only user, the workflow is linear, and I need to debug quickly.

Work Project: LangChain

At work, we built a customer support automation system:

support-architecture.txt
Customer Support Agent
├── Multi-tenant (each customer isolated)
├── Multiple LLMs (GPT-4 for reasoning, Claude for empathy)
├── Vector database (conversation history)
├── Integration with:
│ ├── Zendesk (ticket system)
│ ├── Slack (notifications)
│ ├── Salesforce (customer data)
│ └── Internal APIs (product info)
├── Observability (LangSmith tracing)
└── Multiple developers working on same codebase
support_agent.py
from langchain.agents import AgentExecutor
from langchain.memory import VectorStoreMemory
from langchain.tools import StructuredTool
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from pydantic import BaseModel
import logging
class SupportAgent:
def __init__(self, customer_id: str):
# Multi-model setup
self.reasoning_llm = ChatOpenAI(model="gpt-4")
self.empathy_llm = ChatAnthropic(model="claude-opus-4-20250514")
# Per-customer memory
self.memory = VectorStoreMemory(
customer_id=customer_id,
vectorstore=self._init_vectorstore()
)
# Enterprise integrations
self.tools = [
self._create_zendesk_tool(),
self._create_salesforce_tool(customer_id),
self._create_product_api_tool()
]
self.agent = AgentExecutor(
agent=self._create_agent(),
tools=self.tools,
memory=self.memory,
callbacks=[self._init_tracing()],
max_iterations=10
)
self.logger = logging.getLogger(f"support.{customer_id}")
async def handle_ticket(self, ticket_id: str) -> dict:
"""Process support ticket with full observability."""
try:
result = await self.agent.ainvoke({
"input": f"Handle ticket {ticket_id}"
})
self.logger.info(f"Ticket {ticket_id} resolved")
return result
except Exception as e:
self.logger.error(f"Failed to handle ticket: {e}")
# Fallback to human agent
await self._escalate_to_human(ticket_id, str(e))
raise

Why LangChain works here: Multiple users, complex integrations, observability requirements, and team collaboration.

Common Mistakes I Made

Mistake 1: Over-Engineering Personal Projects

I initially built my personal knowledge agent with:

  • LangChain agent framework
  • Redis for memory
  • PostgreSQL for persistence
  • Prometheus metrics
  • Docker containerization

Result: 50+ lines of YAML configuration for a script I run once per week.

The file-based version: 50 lines of Python, no dependencies beyond the Anthropic SDK.

Mistake 2: Under-Engineering Production Systems

For a work project, I tried to use file-based agents because “it worked for my personal projects.” The result:

production-failure.txt
Issues encountered:
- Race conditions when multiple users accessed same files
- No audit trail for compliance
- Memory leaks (context files grew unbounded)
- No retry logic for failed API calls
- No metrics for debugging production issues
- Hard to onboard new developers (no patterns)

We migrated to LangChain after two production incidents.

Mistake 3: Ignoring Team Capabilities

I once recommended LangChain to a team where:

  • Only one developer knew Python
  • No one had used async/await before
  • No experience with vector databases

Result: Three months of learning curve before shipping a simple feature.

Comparison Summary

comparison-table.txt
| Aspect | LangChain/Semantic Kernel | File-Based |
|---------------------|------------------------------|-------------------------|
| Learning curve | Weeks to months | Hours to days |
| Debugging | Through abstraction layers | Direct file inspection |
| Observability | Built-in (LangSmith, etc.) | Manual implementation |
| State management | Automatic | Manual |
| Multi-tenancy | Built-in patterns | Custom implementation |
| Team scalability | High (patterns/standards) | Low (ad-hoc) |
| Prototyping speed | Slower (setup overhead) | Faster (minimal setup) |
| Production ready | Yes (with configuration) | Needs custom work |
| Non-tech accessible | No | Yes (markdown files) |

Why Frameworks Add Abstraction Layers

LangChain and Semantic Kernel operate at a high abstraction level to solve common problems:

  1. Model switching: Swap between OpenAI, Anthropic, local models with one line change
  2. Memory management: Automatic context window handling
  3. Tool orchestration: Retry logic, error handling, parallel execution
  4. Observability: Built-in tracing and logging

The cost of this abstraction is complexity. You trade direct control for convenience.

The Unix Philosophy Alternative

File-based approaches follow the Unix philosophy:

  • Do one thing well
  • Use text as universal interface
  • Compose small tools

This philosophy excels when your problem fits the pattern: linear processing, text-based context, single user.

When to Migrate

Start simple, add complexity when needed:

migration-signals.txt
Signals it's time to migrate from file-based to framework:
===========================================================
1. You're implementing your own memory management
2. Multiple users need access to the same agent
3. You've added more than 3 custom error handling patterns
4. Debugging takes longer than development
5. You're manually implementing retry logic
6. You need metrics for production monitoring
7. Team members are confused by your folder structure

Summary

The choice between LangChain/Semantic Kernel and file-based approaches isn’t about one being better than the other. It’s about matching the tool to the job.

For personal projects with simple workflows: File-based approaches win. Markdown files are transparent, debuggable, and require minimal learning.

For production systems with complex orchestration: Frameworks like LangChain provide essential patterns for reliability, scaling, and team collaboration.

The best architecture serves your specific use case without unnecessary complexity. Start simple. Add abstraction only when the pain of simplicity exceeds the pain of complexity.

For my weekly summary agent, I went back to the file-based approach. It takes 3 seconds to understand, 10 seconds to debug, and does exactly what I need.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments