The agent framework you can read in one sitting
agentling is a tiny async framework for reliable, observable, tool-using agents: a clean ReAct loop, typed memory, streaming events, recoverable failures, and progressive-disclosure skills, in around 800 lines of Python.
$
~800
lines of source
2
runtime dependencies
100%
typed, mypy checked
1
loop for everything
Why agentling
Everything is a thin layer on one readable loop
An agent is a loop that turns a model, some tools, and a memory of what happened into more actions, until it has an answer. agentling keeps that loop legible and layers everything else on top.
Async first
The loop, tools, and model calls are all async. Tool calls in a single step run concurrently by default.
One code path
Blocking and streaming share the exact same loop. There is a single async generator; blocking mode just drains it.
Typed memory
A run is a list of typed steps that serialize to JSON, so persistence, replay, and multi-turn continuation come for free.
Streaming events
Five frozen event types narrate every run: text deltas, tool calls, results, recorded steps, and the final answer.
Self-healing
A tool that raises becomes an observation the model can recover from, not a crash. Stuck loops get a nudge.
Progressive skills
Drop a SKILL.md folder in and the model sees only its name until it decides to load it. Big skill libraries stay cheap.
The loop
One iteration, one step
Each step streams a model turn, runs the tools it asked for, records the outcome, and checks whether the run is done. Blocking and streaming, persistence and resume, skills and self-healing all hang off this one generator.
- 1Render the promptTyped memory becomes model messages.
- 2Stream the model turnText deltas surface as they arrive.
- 3Run the tool callsConcurrently, with schema validation.
- 4Record the stepUsage, duration, and results, all typed.
- 5Check for an answerfinal_answer, plain text, or step limit.
import asyncio
from agentling import Agent, OpenAIModel, tool
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city.
Args:
city: The city to look up.
"""
return f"It is 22C and sunny in {city}."
async def main() -> None:
agent = Agent(model=OpenAIModel("gpt-4o-mini"), tools=[get_weather])
print(await agent.run("What's the weather in Paris?"))
asyncio.run(main())from agentling import FinalEvent, TextDelta, ToolCallEvent
async for event in agent.run("Summarize this.", stream=True):
if isinstance(event, TextDelta):
print(event.text, end="", flush=True)
elif isinstance(event, ToolCallEvent):
print(f"\n[calling {event.tool_call.name}]")
elif isinstance(event, FinalEvent):
print(f"\nDone: {event.answer}")agent = Agent(
model=OpenAIModel("gpt-4o-mini"),
tools=[read_file],
skills=["skills/code-reviewer"],
)
# The model sees only the skill catalog until it
# decides to call load_skill("code-reviewer").
print(await agent.run("Review src/agentling/agent.py"))session = agent.start()
await session.run("First question")
saved = session.memory.dump_json()
# ...later, even in another process...
restored = agent.start()
restored.memory = Memory.load_json(saved)
await restored.run("A follow-up", reset=False)Observability
Every run narrates itself
Five frozen event types describe everything an agent does. Feed them to a terminal, a UI, a logger, or a metrics store; the stream and the durable memory can never disagree.
Read the source. All of it. Tonight.
No metaclasses, no plugin registry, no DSL. Install it, read the loop, and know exactly what your agent will do.