betaloop
A reusable, storage-free ReAct agent kernel: the engine, framework and
protocols that drive a tool-calling agent, plus optional capability bundles. It
knows nothing about how runs are stored (or even whether they are) — persistence
is an optional EventSink a host plugs in. Any application (a thesis-writing
platform, a coding agent, ...) implements its own tools + prompt + storage and
reuses this kernel.
Core (zero I/O, zero business)
runtime—AgentRuntimeReAct loop + event stream. Supports cancellation (run(..., stop=Event|callable)→cancelledevent,status="cancelled") and streaming (LLMConfig(stream=True)→assistant_deltaevents while the model generates; a final fullassistantevent always follows). Every model call emits ausageevent — prompt/completion/total tokens, that call's cost, and context fullness (context_tokens,context_chars,context_window,context_percentwhenLLMConfig(context_window=...)is set) — so a frontend can show live token/context gauges;RunStatsand the hostdoneevent carry the cumulative breakdown. Malformed tool results instead of executing with empty/wrong args. A run that exhausts its step budget gets a forced toolless wrap-up call (tool_choice="none") so it ends with the model's summary, reportingstatus="max_steps"(and never executing the stubborn model's further tool calls).LLMConfig(temperature=..., max_tokens=...)are forwarded on every call. Tool calls execute in model order with consecutive READ tools parallel and every WRITE/META tool alone (no write races);ToolSpec(timeout=...)cancels a hung call. A mid-run context budget (context_budget, default 400k chars) shrinks old tool results head+tail so long runs don't blow the context window. Sinks are error-isolated (a broken display/record sink logs instead of killing the run;strict_records=Trueopts record failures back into fatal).llm— OpenAI-compatible chat client (retry / jittered backoff / response-shape validation /Retry-After-aware 429 handling / fatal-4xx fail-fast) + SSE streaming helpers; transports normalize usage toprompt_tokens/completion_tokens/total_tokensacross chat-completions and Responses shapestools—ToolRegistry(register / unregister / dispatch / mode filtering / argument validation / per-tool timeout) + pre-dispatch middleware viaadd_middleware(audit / quota / human-in-the-loop confirmation of write tools)actions—Action+UndoEngine(pure, storage-free undo; reverters may be sync or async)memory—replay_messages/recap_text+MemoryProviderevents—EventSink(display + record channels) + SSE serializationcontext/modes—AgentContext+AgentMode/ToolCategory; host-defined modes viaregister_mode(name, categories)(unknown modes raise instead of silently degrading to read-only)
Optional bundles (betaloop.bundles)
-
host—AgentHosthost-adapter framework: message assembly, run envelope (run_start/done), error funneling,StoreSink(persist via a store),undo_run(reverters see the host'sextracontext),DictToolAdapter(wrap a dict-based tool system). Eliminates the per-host boilerplate round 1 left behind.host.run(..., stop=...)forwards cancellation to the runtime. -
store—RunStore/ConversationStore/BlobStoreProtocols +JsonlRunStore(default, zero-database JSONL + content-addressed blob spillover). Hosts wanting a DB implement the Protocols; the default needs none. Action values larger thanspill_threshold(default 8KB) externalize to blobs and rehydrate transparently on read; id counters are in-memory so appends don't rescan the stream. -
subagents—SubagentEngine+SubagentRoster/SubagentSpec+delegatetool: isolated worker agents the orchestrator hands subtasks to, tagged so undo still reverts them while the orchestrator's context stays lean.delegate_parallelfans independent tasks out concurrently (bounded bymax_parallel, failures isolated per agent);SubagentEngine(on_subagent_event=...)streams livesubagent_progressheartbeats to a host push channel;SubagentSpec(transport=...)routes a subagent to a different endpoint. -
admin—tool_categories/list_tools_admin/list_tool_packages_admin/check_packagesover a registry + display packages (admin-panel source; packages are grouping only, tools stay per-name togglable). -
workspace— sandboxed file I/O + read/write/edit/list/search/glob tools + file undo reverters.edit_filerefuses ambiguousold_text(multi-match) unlessreplace_allis set and returns a diff;search_filesgreps content by regex (dir / glob filters),glob_filesmatches paths by pattern;read_filesupportsoffset/limitline-window reads. -
images— tool-tier image perception, no kernel changes.image_info: stdlib-only header probe (PNG/JPEG/GIF/BMP/WEBP — dimensions, dpi, color mode) answering deterministic questions with zero model calls.analyze_image: ONE vision-model call (OpenAIimage_urldata-URL block + the question), registered only when aLLMConfigis passed — the host's main config reuses the main model, a dedicated one routes vision elsewhere. Images never enter the main conversation (answers are memoized per file hash + question), so context budget / trimming / replay stay untouched. -
sandbox— Python code execution (bubblewrap or passthrough backend); output truncation keeps head+tail so tracebacks at the end stay visible -
skills— markdown skill libraries (SkillLibraryflat dir; package-awareSkillPackageswithRemoteSkillSourceregistry mirrors) +load_skilltool. Skills do file/network I/O, hence a bundle —import betaloopstays zero-I/O (deprecatedbetaloop.skillsalias kept). -
mcp—MCPManager+MCPServerConfig+parse_servers: bridge external MCP servers (stdio transport, e.g.npx -y @z_ai/mcp-server) into the registry. Sessions outlive registry rebuilds and lazily self-heal: a dead session is restarted on the next tool call, no re-attach needed.readOnlyHintannotations map to the READ category; MCP tools carry no undo reverters. Stdlib-only (newline-delimited JSON-RPC), secrets stay host-side:from betaloop.bundles import MCPManager, MCPServerConfig manager = MCPManager([MCPServerConfig( name="zai", command=["npx", "-y", "@z_ai/mcp-server"], env={"Z_AI_API_KEY": "...", "Z_AI_MODE": "ZHIPU"}, default_category="read")]) await manager.attach(registry) # registry gains zai__* tools
Install
pip install betaloop
# local dev (editable + test/lint deps):
pip install -e ".[dev]"
Storage model
The kernel stores nothing. A host provides:
- an
EventSink(write side) — persists message records however it likes (DB / file / nowhere); - a
MemoryProvider(read side, optional) — replays prior turns; - an
UndoEnginefed from wherever the host kept actions.
Minimal host sketch
from betaloop import LLMConfig, ToolRegistry
from betaloop.bundles import AgentHost, JsonlRunStore
from betaloop.bundles.workspace import register_file_tools
registry = ToolRegistry()
register_file_tools(registry, lambda ctx: f"/data/{ctx.user_id}") # your files
store = JsonlRunStore("/var/lib/myapp/agent") # zero-DB default
host = AgentHost(registry,
LLMConfig(model=..., base_url=..., api_key=...),
store, build_system_prompt=my_prompt_builder)
ctx = AgentContext(run_id=rid, user_id=uid)
async for event in host.run(ctx, task, history=prior_turns):
... # forward run_start / step / tool_call / tool_result / done to your frontend
A host supplies only its tools, system prompt, and (optionally) a store
backend — the engine, persistence, run envelope, undo, and (via subagents)
delegation are all reused.
More
examples/minimal_host.py— a runnable, offline minimal host (tools → run → events → undo).CHANGELOG.md— what changed and when.
License
MIT
Release files for betaloop 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| betaloop-0.7.0.tar.gz | 129.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| betaloop-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 225.2 kB
Release files / betaloop-0.7.0.tar.gz
| Download URL | betaloop-0.7.0.tar.gz |
|---|---|
| Size | 129.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a7d0252aaa87595f3ce4525406a7d20367fd77d636124893d0053aab4bd1d7ff
|
|
BLAKE2b-256 checksum How to use checksums |
4276ce045cb82cb9c59c3620866c040f951618659b3fb8360f7c185cd2b1f2a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / betaloop-0.7.0-py3-none-any.whl
| Download URL | betaloop-0.7.0-py3-none-any.whl |
|---|---|
| Size | 95.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
531f16b55fee0ea8d07ec04c67dc52ca4e5a74cf17b3ca39ea1a99b30fab6099
|
|
BLAKE2b-256 checksum How to use checksums |
7c907c2ce3b8ba90a0dec5c07cb2f9441bd30a3c542c2ebfb9199bf31f053f51
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|