An agent loop, hand-written. One line at a time. No magic.
A few hundred lines of Python — tool dispatcher, message protocol, streaming client, all in the open. Small enough to read in one sitting.
What is this · Quick Start · Library · Design · 简体中文
Bryx CLI is an agent loop, hand-written in a few hundred lines of Python. No LangChain, no LlamaIndex, no AutoGen. The tool dispatcher, the message protocol, the streaming client — every piece is out in the open.
After a while building with agent frameworks, I realized I couldn't explain how one actually worked under the hood. So I tore it down to the bones and rebuilt it. Turns out an agent really is just an LLM, a tool loop, and some state — small enough to read in one sitting. That's the point.
If you want to ship a product fast, use a framework. Bryx CLI is for when you want to understand what's happening — to debug a tool dispatch without digging through six layers of abstraction, or to tweak the main loop without reading 20k lines of someone else's code first.
It's also a decent scaffold if you have opinions about how an agent should behave and don't want to fight a framework to express them.
Shipped
- LLM client — Anthropic SDK wrapper with streaming, thinking mode, usage tracking
- Tool registry — register handlers, export schemas, async dispatch
In progress
- Agent loop —
reason → act → observewith error backoff
Planned
- Built-in tools —
read,bash,edit,write - Context compaction — auto-summarize when the message log grows too long
- Skills — load
skills/<name>/SKILL.mdon demand - Global memory —
MEMORY.mdsummary injected into context - Sub-agents —
create_agentspawns an isolated child - CLI REPL / HTTP API
- Self-evolution loop — propose → apply → evaluate, each attempt on its own Git branch
First milestone: rebuild the core of an agent I wrote previously, by hand. Start from something that works, not a toy.
Python 3.14+. I use uv.
git clone https://github.com/loonghao/bryx.git
cd bryx
uv sync
# drop your API key in .env
cat > .env <<'EOF'
ANTHROPIC_API_KEY=sk-...
ANTHROPIC_BASE_URL=https://api.anthropic.com
ANTHROPIC_MODEL=claude-opus-4-8
EOF
uv run main.pyWorks with anything that speaks the Anthropic wire format — Claude, DeepSeek through a gateway, etc. Just swap ANTHROPIC_BASE_URL.
import asyncio
from datetime import datetime
from src.agent.tools import ToolDef, ToolRegistry
async def now_handler(inp: dict) -> str:
return datetime.now().isoformat()
NOW_SCHEMA = {
"name": "now",
"description": "get current date and time",
"input_schema": {"type": "object", "properties": {}, "required": []},
}
registry = ToolRegistry()
registry.register(ToolDef(schema=NOW_SCHEMA, handler=now_handler))
result = asyncio.run(registry.dispatch("now", {}))
print(result) # 2026-06-30T12:34:56.789012 ┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ main.py │ ───▶ │ Agent loop │ ───▶ │ LLM client │
│ (entry) │ │ (core.py) │ │ (llm.py) │
└──────────┘ └──────┬───────┘ └──────────────┘
│
▼
┌──────────────┐
│ ToolRegistry │
│ (tools.py) │
└──────────────┘
llm.py— thin wrapper around the Anthropic SDK. Streaming, thinking budget, token accounting. Swappablebase_urlfor any compatible gateway.tools.py— register a handler, get schema export and async dispatch. No magic, just adictand anawait.core.py— the main loop (coming soon).reason → act → observe, repeat.
- Only the official
anthropicSDK. No orchestration libraries. - Code stays flat. No inheritance trees, no abstract base classes with one implementation.
- Make it run first. Make it pretty later.
- Stick to the Anthropic wire format. No custom message protocol.
Early stage. If you find a bug, have a use case, or want to port a piece to another LLM provider — open an issue. PRs welcome, with fair warning: I'm opinionated about keeping the codebase small.
MIT.