A mini LLM agent framework with function-based tools and sub-agent spawning.
The core codebase is compact: about 3000 lines in src/xun/*.py, with comprehensive type hints.
Requires Python 3.12+ (PEP 695)
# 1. Install dependencies
pip install git+https://github.com/MenxLi/xun.git
# 2. Install Playwright browsers (if using the default browser tools)
playwright install
# 3. Configure environment variables (see `Configuration` section below)
vim .env
# 4. Run the agent in interactive mode
xunOptionally, run the agent in web mode:
# - Build the web frontend (if using the web display)
make web-build
# - Start the web server at current directory
xuns .Basic: Quickly set up an agent with plain functions as tools — no decorators, no classes needed.
from xun import setup_agent
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
agent = setup_agent(tools = [add])
agent.instruct("Add 2 and 3.").execute()Advanced: The framework is flexible and extensible. Additional features are shown in demo.ipynb, including:
Agentconfiguration- Display extension
- Output validation
- Tool attributes
- Context injection
- Type-state transition
- Sub-agent spawning
- Lifecycle hooks
- ...
Do check out demo.ipynb for detailed examples.
Run xun in your terminal to start an interactive session.
You can also pass a prompt as an argument to begin with a specific instruction.
xun "Write a hello world python script and save it to hello.py"Image attachments are supported in the format of [image:path_or_url]. For example:
>>> [image:cat.png image:https://example.com/dog.png] compare them.
Input /help to see the full list of commands.
WebDisplay provides an interactive web interface for the agent.
It can be used as a chat-based web application, or as a backend for other applications.
from xun import WebDisplay, WebDisplayService, setup_agent
display = WebDisplay(expose_files=True)
agent = setup_agent(display=display, default_tools=True)
service = WebDisplayService().mount("/", display)
service.start(blocking=True)Open any tokenized URL printed at startup. The service exchanges its query token for one HttpOnly cookie scoped to /, so the browser can access every mounted display without logging in again. API clients can use Authorization: Bearer <token>.
File browsing, upload, download, and deletion are disabled unless expose_files=True.
The agent will start in web mode, and you can access it via the printed URL.
Multiple displays can share one authenticated service. Each mount keeps its own agents, event history, and file policy:
service = WebDisplayService()
service.mount("/research", research_display)
service.mount("/coding", coding_display)
service.start(blocking=True)display.build_routes() and display.build_app() do not add authentication. Use WebDisplayService for the authenticated server, or provide authentication and lifecycle handling in your own ASGI host.
Frontend development
The frontend development command starts both the backend and Vite with Vue DevTools:cd web
npm install
npm run devOpen http://127.0.0.1:5173. Build a production bundle with npm run build. See web/README.md for connecting the UI to a separately managed backend.
xun reads its configuration from ~/.xun/config.json (the location can be overridden with the XUN_HOME environment variable). The file is optional:
if it does not exist, built-in defaults are used, and no files are created.
The file only needs to contain the fields you want to change. For example, to just override the model:
{
"model": {
"name": "my-model"
}
}The config supports ${XUN_...} placeholders which are substituted from environment variables (e.g. ${XUN_OPENAI_API_KEY}), so secrets can live in a .env file instead. A placeholder with no matching environment variable causes a startup error.
| Config field | Built-in default | Description |
|---|---|---|
provider.openai_base_url |
${XUN_OPENAI_BASE_URL} |
OpenAI-compatible API endpoint. |
provider.openai_api_key |
${XUN_OPENAI_API_KEY} |
API key. |
model.name |
${XUN_OPENAI_MODEL} (empty) |
Model identifier. If the resolved value is empty, available models are auto-detected from the API. |
model.capabilities |
["vision"] |
Capabilities exposed to the model (e.g. vision for image input). |
auto_confirm |
false |
Auto-approve actions without prompting. |