Metadata-Version: 2.4
Name: betaloop
Version: 0.6.0
Summary: A reusable, storage-free ReAct agent kernel (engine + framework + protocols) with optional capability bundles.
Project-URL: Changelog, https://github.com/betaloop/betaloop/blob/main/CHANGELOG.md
Project-URL: Examples, https://github.com/betaloop/betaloop/tree/main/examples
Keywords: agent,llm,react,kernel,tool-calling,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"

# 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` — `AgentRuntime` ReAct loop + event stream. Supports cancellation
  (`run(..., stop=Event|callable)` → `cancelled` event, `status="cancelled"`)
  and streaming (`LLMConfig(stream=True)` → `assistant_delta` events while the
  model generates; a final full `assistant` event always follows). Every model
  call emits a `usage` event — prompt/completion/total tokens, that call's
  cost, and context fullness (`context_tokens`, `context_chars`,
  `context_window`, `context_percent` when `LLMConfig(context_window=...)` is
  set) — so a frontend can show live token/context gauges; `RunStats` and the
  host `done` event 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, reporting `status="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=True`
  opts 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 to
  `prompt_tokens`/`completion_tokens`/`total_tokens` across chat-completions
  and Responses shapes
- `tools` — `ToolRegistry` (register / unregister / dispatch / mode filtering /
  argument validation / per-tool timeout) + pre-dispatch **middleware** via
  ``add_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` + `MemoryProvider`
- `events` — `EventSink` (display + record channels) + SSE serialization
- `context` / `modes` — `AgentContext` + `AgentMode` / `ToolCategory`;
  host-defined modes via ``register_mode(name, categories)`` (unknown modes
  raise instead of silently degrading to read-only)

## Optional bundles (`betaloop.bundles`)
- `host` — `AgentHost` host-adapter framework: message assembly, run envelope
  (`run_start`/`done`), error funneling, `StoreSink` (persist via a store),
  `undo_run` (reverters see the host's `extra` context), `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`/`BlobStore` Protocols +
  `JsonlRunStore` (default, **zero-database** JSONL + content-addressed blob
  spillover). Hosts wanting a DB implement the Protocols; the default needs
  none. Action values larger than `spill_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` + `delegate`
  tool: isolated worker agents the orchestrator hands subtasks to, tagged so undo
  still reverts them while the orchestrator's context stays lean.
  `delegate_parallel` fans independent tasks out concurrently (bounded by
  `max_parallel`, failures isolated per agent); `SubagentEngine(on_subagent_event=...)`
  streams live `subagent_progress` heartbeats 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_packages` over 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_file` refuses ambiguous `old_text` (multi-match)
  unless `replace_all` is set and returns a diff; `search_files` greps content
  by regex (dir / glob filters), `glob_files` matches paths by pattern;
  `read_file` supports `offset`/`limit` line-window reads.
- `sandbox` — Python code execution (bubblewrap or passthrough backend);
  output truncation keeps head+tail so tracebacks at the end stay visible
- `skills` — markdown skill libraries (`SkillLibrary` flat dir; package-aware
  `SkillPackages` with `RemoteSkillSource` registry mirrors) + `load_skill`
  tool. Skills do file/network I/O, hence a bundle — `import betaloop` stays
  zero-I/O (deprecated `betaloop.skills` alias 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.
  `readOnlyHint` annotations map to the READ category; MCP tools carry no undo
  reverters. Stdlib-only
  (newline-delimited JSON-RPC), secrets stay host-side:

  ```python
  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
```bash
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 **`UndoEngine`** fed from wherever the host kept actions.

## Minimal host sketch
```python
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
