Note: This document is also available in Spanish as README.es.md.
A small, reusable local agent that calls the LM Studio HTTP API directly. It can hold conversations, switch profiles, and run tools without using the OpenAI SDK.
- Python 3.12 or later.
- Poetry.
- LM Studio with the server running and a model loaded.
- An LM Studio API key if authentication is enabled.
- Firecrawl only for profiles that use web search.
Copy .env.example to src/agent/.env and fill in the private values. The
.env file is ignored by Git and must not be included in commits.
LM_STUDIO_URL=http://localhost:1234/v1
LM_STUDIO_API_KEY=replace-with-your-key
LM_STUDIO_MODEL=replace-with-the-loaded-model-id
FIRECRAWL_URL=https://api.firecrawl.dev/v2/search
FIRECRAWL_API_KEY=replace-with-your-keyLM_STUDIO_URL normally uses HTTP for the local LM Studio server. Use HTTPS
only when an actual TLS proxy is running in front of the server.
Firecrawl supports two explicit response contracts:
- A URL containing
/v1/must returndataas a list. - A URL containing
/v2/must returndata.webas a list.
A response that does not match the version in its URL is treated as an error, not as an empty search.
| Variable | Default | Purpose |
|---|---|---|
DEFAULT_AGENT |
research |
Initial profile |
LM_STUDIO_TIMEOUT |
120 |
LM Studio timeout in seconds |
FIRECRAWL_TIMEOUT |
30 |
Firecrawl timeout in seconds |
HTTP_RETRIES |
2 |
Retries for network failures, 429, and 5xx errors |
MAX_TOOL_ROUNDS |
5 |
Maximum number of tool rounds |
MAX_HISTORY_MESSAGES |
40 |
Approximate conversation-history limit |
The original command still works:
poetry run python src/agent/agent.pyYou can also run the package:
PYTHONPATH=src poetry run python -m agentSelect an initial profile with:
poetry run python src/agent/agent.py --agent analyst/agents list profiles
/agent NAME switch profiles and start a new conversation
/new clear the current conversation
/history show a summary of the stored history
/help show help
/quit exit
You can also exit by entering exit, quit, q, or bye, in any combination
of uppercase and lowercase letters. These commands are handled locally and do
not call LM Studio.
Switching profiles resets the conversation so incompatible system instructions are not mixed together.
research: web research with required sources.analyst: analysis that separates facts, assumptions, and conclusions.general: local conversation without external tools.writer: writing and rewriting with more creative variation.
Each profile defines only three things: a system prompt, tool names, and
generation parameters. Profiles are stored in src/agent/profiles.py.
- The CLI adds the question to the conversation history.
- The runner sends the history, profile, and tools to LM Studio.
- If the model requests tools, Python validates and executes each call.
- Structured results are added to the history with the
toolrole. - LM Studio receives the updated history and decides whether to search again or respond.
- When the tool limit is reached, one final request is made without tools, guaranteeing an opportunity to synthesize the results.
- The CLI prints one response and keeps the most recent complete interactions.
The model never executes Python itself. It only requests tools through the protocol; the dispatcher decides which functions are allowed.
src/agent/
├── agent.py entry point compatible with the original script
├── __main__.py entry point for `python -m agent`
├── cli.py commands and interactive session
├── config.py environment loading and validation
├── http_utils.py JSON POST requests and bounded retries
├── lm_studio.py LM Studio chat transport
├── profiles.py profiles and generation parameters
├── runner.py model-tool-model loop
└── tools.py schemas, Firecrawl, and tool dispatcher
The implementation uses functions and dictionaries. The only objects with a
lifecycle are the httpx clients, because they maintain reusable HTTP
connections.
Add an entry to AGENT_PROFILES in profiles.py:
"translator": {
"description": "Translation without external tools",
"system_prompt": "Translate faithfully and preserve formatting.",
"tool_names": [],
"generation": {
"temperature": 0.1,
"top_p": 0.9,
"max_tokens": 1600,
},
},The runner and CLI do not need to be changed.
A tool requires three changes in tools.py:
- Add its schema to
TOOL_DEFINITIONS. - Write a handler with simple arguments.
- Register the handler in
TOOL_HANDLERS.
Then add its name to the profiles that are allowed to use it. An unknown name or invalid JSON produces a controlled error result for the model.
Each turn displays:
- the current loop phase;
- response time;
finish_reason;- token usage when LM Studio includes it;
- the approximate message and character count of the history;
- the name, status, and result count for each tool.
Configuration errors appear before the session starts. Tool errors are returned to the model so it can explain them. The CLI rolls back a failed interaction so it does not affect the next question. API keys are never printed.
poetry run pytest -qThe tests do not access LM Studio or Firecrawl. They use local HTTP transports and cover v1/v2 contracts, authentication, retries, tools, final synthesis, invalid responses, and conversation-history trimming.