I Built An AI Agent without Langchain/Vibe Coding, And It's Very Easy!
Most AI agent tutorials hide the hard parts inside a framework.
I wanted to see the hard parts. So I skipped the framework entirely.
What I built
A working ecommerce AI agent using raw Anthropic SDK and TypeScript. No LangChain. No AutoGPT. No abstractions I didn't write myself.
The agent handles real questions:
- "Do you have wireless earbuds in stock?"
- "What's the status of order ORD123?"
- "What's your return policy?"
And it figures out which tool to call on its own. I never write a single if/else to route messages.
The thing that surprised me most
The entire agent is a while loop.
while (true) {
const response = await llm(messages);
if (noToolCalls) break; // Claude answered directly
await runTools(toolCalls); // Claude needs data first
messages.push(toolResults); // feed back, loop again
}
That's it. That's what LangChain is abstracting. A loop, a tool lookup, and a result push. Once I saw it written out like this, every "agent framework" started looking like overkill for most use cases.
What makes it an agent and not a chatbot
The difference is one thing: the model decides what to call.
In a chatbot, you hardcode routing, "if the user says order, call the order function." In an agent, you give Claude a list of available tools with descriptions, and Claude reads the user's message and decides which tools it needs and sometimes multiple, sometimes none.
// You send this to Claude
tools: [
{ name: "search_products", description: "Search catalog by keyword" },
{ name: "get_order_status", description: "Get order status by ID" },
{ name: "get_return_policy", description: "Get return and refund policy" },
]
// Claude responds with this when it needs data
{
"type": "tool_use",
"name": "search_products",
"input": { "query": "wireless earbuds" }
}
Claude chose search_products. You didn't tell it to. That choice... that's the agent.
The folder structure that actually scales
src/
├── agent/EcommerceAgent.ts # the while loop
├── tools/
│ ├── index.ts # registry — add tools here
│ ├── searchProducts.ts # one file per tool
│ ├── getOrderStatus.ts
│ └── getReturnPolicy.ts
└── data/
├── products.ts # swap for Postgres later
└── orders.ts
One tool per file. Adding a new tool means one new file and one line in the registry. The agent loop never changes.
That's not over-engineering, that's the exact seam you need when this scales to a real product.
What I'd do differently in production
Today the data is hardcoded arrays. In production:
products.tsbecomes a pgvector semantic search queryorders.tsbecomes a Postgres repository- Message history moves from in-memory to Redis
- Write actions (like creating a support ticket) get wrapped in a Command with an audit trail
The agent layer stays identical. Only the data layer changes. That's the whole point of structuring it this way from day one.
Watch the full build
I recorded the entire thing from empty folder to working agent in 37 minutes. Link in comment
No cuts, no skipping the hard parts, no framework magic.
If you've been frustrated by LangChain tutorials that don't explain what's actually happening, this one's for you.