PythonTypeScriptLow abstraction — you write the loop
No framework (LLM SDK + application code)
Nothing extra — the provider SDK already gives you messages, tool definitions, and structured output; a 30-line loop in your own code is the entire agent runtime.
Architecture
- Messages array is the state: system prompt, user turns, assistant tool calls, tool results. You own it, so you can trim, summarise, or persist it however you like.
- Tool registry is a dict from tool name to a plain function plus its JSON schema. Validation is whatever you write (
pydantic,zod, hand-rolled). - The loop: call the model, execute any tool calls, append results, repeat until the model stops or a budget (iterations, tokens, seconds) is exhausted.
- Everything else is ordinary software: retries with backoff, idempotency keys, logging spans, approval gates, tests — the same code you would write for any I/O-bound service.
Best use cases
- Production systems where you must understand every prompt and token that leaves the process.
- Teams with solid software-engineering habits and one or two providers.
- Learning: you cannot reason about a framework until you have written the loop it wraps.
- Latency- and cost-sensitive paths where a framework's hidden prompt scaffolding is unacceptable.
Weaknesses
- You re-implement provider-agnostic message formats, streaming, and tool-call parsing yourself if you switch vendors.
- Checkpointing, human-in-the-loop resumption, and durable execution are yours to build — this is where teams underestimate the work.
- No shared vocabulary with other teams; every codebase invents its own
Agentclass. - Integrations (vector stores, document loaders, observability exporters) are individually wired, not plugged in.
When NOT to use it
- You need durable, resumable, multi-step workflows with checkpoints and time-travel debugging and do not want to build a state machine runtime.
- You are prototyping across many providers and vector stores and want swappable adapters more than control.
- The team has no appetite for owning the loop and would rather own a framework upgrade cadence.
Code example
Illustrative — APIs change between versions.
1import json2 3TOOLS = {"get_weather": get_weather, "search_docs": search_docs} # plain functions4SCHEMAS = [weather_schema, search_schema] # JSON Schema per tool5 6def run_agent(user_msg: str, max_steps: int = 8) -> str:7 messages = [{"role": "system", "content": SYSTEM}, {"role": "user", "content": user_msg}]8 for _ in range(max_steps): # hard iteration budget9 reply = call_model(messages, tools=SCHEMAS) # your SDK wrapper; shape is provider-specific10 messages.append(reply.as_message())11 if not reply.tool_calls: # model answered in prose -> done12 return reply.text13 for call in reply.tool_calls:14 try:15 result = TOOLS[call.name](**call.arguments)16 except Exception as e: # errors go back to the model, not up the stack17 result = {"error": type(e).__name__, "detail": str(e)[:200]}18 messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})19 raise RuntimeError("step budget exhausted")