Quickstart¶
Five minutes from zero to a running agent you understand every line of.
The whole looplet mental model fits in one turn:
- The LLM proposes a tool call.
- The registry validates and dispatches it.
- Hooks observe or steer the turn.
- State records the step.
- The loop yields a
Stepback to your code.
Everything below is ordinary Python around that mechanism.
1. Install¶
Cold import: 289 ms
looplet has zero required runtime dependencies. The [openai] /
[anthropic] extras are imported lazily only when you instantiate
a backend.
2. Point it at any OpenAI-compatible endpoint¶
export OPENAI_BASE_URL=https://api.openai.com/v1 # or Ollama, Groq, Together, vLLM, …
export OPENAI_API_KEY=sk-…
export OPENAI_MODEL=gpt-5.5
Run the bundled hello-world to sanity-check the wiring:
You should see three lines of #1 greet(name='…') → {…} [Xms] trace
followed by a final #N ✓ done(...). If that works, you are ready.
3. Write your first loop¶
from looplet import (
composable_loop, LoopConfig, DefaultState,
OpenAIBackend, tool, tools_from,
)
llm = OpenAIBackend(base_url="https://api.openai.com/v1",
api_key="sk-...", model="gpt-5.5")
@tool(description="Search the docs.")
def search(query: str) -> dict:
return {"results": [f"result for {query}"]}
tools = tools_from([search], include_done=True)
# Run. You own the iteration.
for step in composable_loop(
llm=llm,
tools=tools,
state=DefaultState(max_steps=5),
config=LoopConfig(max_steps=5),
task={"goal": "What is looplet?"},
):
print(step.pretty())
That's it. The whole agent is 30 lines.
The objects map directly to the mental model:
tools_from(...)builds the registry that validates and dispatches tool calls.hooks=[...]lets plain Python objects observe or steer the loop.DefaultStaterecords the steps and remaining budget.composable_loop(...)yields eachStepso your code can print, test, stop, or route it.
4. Add a hook¶
Hooks are plain classes. Implement only the methods you want — the loop
checks with hasattr before calling.
from looplet import HookDecision
class StepBudget:
"""Cap the loop at N productive steps; block ``done()`` until
we've gathered enough evidence."""
def __init__(self, max_productive: int) -> None:
self.cap = max_productive
self.productive = 0
def post_dispatch(self, state, session_log, tool_call, tool_result, step_num):
if not tool_result.error and tool_call.tool != "done":
self.productive += 1
def should_stop(self, state, step_num, new_entities):
if self.productive >= self.cap:
return HookDecision(stop="step_budget_exceeded")
return False
# ... then in composable_loop(...):
hooks=[StepBudget(max_productive=8)]
For real token-cost tracking, wire a BackendRouter
(it owns the token counters) and read router.total_input_tokens
/ router.total_output_tokens between steps.
See Hooks for the full protocol and a dozen recipes.
5. Capture the trajectory¶
from looplet import ProvenanceSink
sink = ProvenanceSink(dir="traces/run_1", redact=lambda s: s.replace("secret", "[REDACTED]"))
llm = sink.wrap_llm(OpenAIBackend(base_url="https://api.openai.com/v1",
api_key="sk-...", model="gpt-5.5"))
for step in composable_loop(llm=llm, tools=tools, hooks=[sink.trajectory_hook()], ...):
print(step.pretty())
sink.flush() # writes trajectory.json + steps/*.json + call_*.txt
Inspect it from the shell:
6. Turn debugging into an eval¶
from looplet import eval_mark
@eval_mark("verdict")
def eval_returns_answer(ctx):
return "answer" in ctx.final_output
@eval_mark("budget")
def eval_stopped_cleanly(ctx):
return ctx.completed # stop_reason == "done"
Run against any saved trajectory:
7. Run or share a bundle¶
A bundle is a portable skill folder that builds normal looplet primitives. It is the beginner-friendly way to run or share a complete capability without hiding the underlying loop.
python -m looplet run ./skills/coder "Fix the tests" --workspace .
python -m looplet blueprint ./skills/coder --workspace .
python -m looplet export-code ./skills/coder coder_agent.py
Advanced users can package an importable looplet factory as a bundle:
python -m looplet package my_agent:build ./skills/my-agent \
--name my-agent \
--description "Run my custom looplet agent."
Claude/Agent Skills-style folders can be wrapped when they are instruction-only, and looplet reports adapter gaps when scripts or resources need explicit tools:
Next steps¶
- Tutorial — hooks, compaction, crash-resume, approval, in five steps.
- Recipes — Ollama, MCP, cost accounting, multi-model routing.
- Skills — lazy skills, runnable bundles, blueprints, and Claude Skill wrapping.
- Pitfalls — ten sharp edges worth knowing.
- Hooks reference — every extension point, every signature.
Where are the sub-agents, the planner, the memory manager?
There aren't any. A sub-agent is a function that calls
composable_loop and returns a value — then you expose it as a
ToolSpec. A planner is a hook that inspects session_log and
returns an InjectContext(...) in pre_prompt. A memory manager
is StaticMemorySource plus a compact_service. Nothing is
hidden.