Tiny Agents
Small building blocks for Python agents and harnesses.
v0.2.4: parallel tools, guarded commands, more work and safety.
Tiny Agents is a framework for building custom agent harnesses. Version 0.2.4 adds concurrent tool execution within each model turn, optional serialization for tools that share state, approval prompts for selected tools, and a more responsive panel. AgentHarness connects model/provider selection, clients, tools, conversation context, and panel callbacks. Application configuration and harness creation stay in main.py; reusable wiring lives in the framework. The framework also includes OpenAI-compatible provider clients, JSON-based tool calling, in-memory conversation context, and terminal formatting utilities.
The core works independently of the panel. The core does not import panel code; AgentHarness accepts a worker and UI callbacks; the panel is an optional dependency. Tool handlers do not need to import the panel and can accept a write callback when they need to stream output. The same core and tools can run in a regular terminal.
For step-by-step examples, see the usage guide. The guide covers direct model calls, adding tools, context management, provider setup, and assembling the panel harness.
Function organization
| Module | Main responsibilities |
|---|---|
tinyAgents/ |
Public imports: tinyAgents, tinyAgents.core, tinyAgents.harness, tinyAgents.panel, tinyAgents.colors, and tinyAgents.formatting. |
src/tinyAgents.py |
startClient, model request functions, tool loading and execution, context storage and compaction. |
src/AgentHarness.py |
AgentHarness: connects provider/model state, tools, requests, context, worker, and panel callbacks. |
src/Panel/Panel.py |
initPanel, Command, PanelContext, AsyncWorker, command dispatch, and terminal UI. |
Tools/ |
Tool schemas, handler mappings, and tool implementations such as hourNow and shell commands. |
Packages/ |
Presentation helpers such as prettyResolve and terminal colors. |
main.py |
Application configuration and assembly: profiles, prompt, tool setup, commands, panel settings, and startup. |
Keep application-specific behavior and configuration in main.py or app modules. Reusable request and wiring behavior belongs in the framework modules; tool implementations belong in Tools/.
The package name used by pip install does not determine Python import names. The public imports are available in short or module-specific forms:
from tinyAgents import AgentHarness, startClient, messageToLLM
from tinyAgents.core import loadTools
from tinyAgents.harness import AgentHarness
from tinyAgents.panel import initPanel, Command, AsyncWorker
from tinyAgents.colors import BLUE, WHITE
from tinyAgents.formatting import prettyResolve
Providers and models
| Provider | Models |
|---|---|
| NVIDIA NIM | openai/gpt-oss-20b, nvidia/nemotron-3-super-120b-a12b, z-ai/glm-5-3-flash |
| Cloud Ollama | gpt-oss:20b |
Installation
The framework requires Python 3.10 or newer.
The distribution name is by-tiny-agents; Python imports use tinyAgents.
After the package is published to PyPI, install the core with:
python -m pip install by-tiny-agents
To install the optional panel dependencies too:
python -m pip install "by-tiny-agents[panel]"
Install the core and panel dependencies from a repository checkout:
uv sync --extra panel
Or use pip:
python -m pip install ".[panel]"
To install directly from GitHub with pip, use:
python -m pip install "by-tiny-agents[panel] @ git+https://github.com/kauanmezavila/tiny-agents.git"
The installable package includes the core, harness, panel, tools and their JSON schemas, and Packages helpers. main.py remains the repository's configurable example harness.
Create a .env file in the project root:
API_KEY_NVIDIA=your-nvidia-api-key
API_KEY_OLLAMA=your-ollama-api-key
The example reads provider keys from these environment variables. Configure available providers and model IDs in profiles in main.py. startClient uses the NVIDIA and Cloud Ollama endpoints for those known providers; other providers can provide baseUrl in their profile. Use models that support function tool calling when enabling tools.
Panel example
The repository's main.py is the runnable panel example. Keep application-specific settings there, then create an AgentHarness to connect them to the framework:
import os
from dotenv import load_dotenv
from prompt_toolkit.patch_stdout import patch_stdout
from tinyAgents import *
from tinyAgents.panel import initPanel, PanelContext, Command, AsyncWorker
from tinyAgents.colors import *
agent_worker = AsyncWorker()
load_dotenv()
apiKeyNvidia = os.getenv("API_KEY_NVIDIA")
apiKeyOllama = os.getenv("API_KEY_OLLAMA")
profiles = {
"nvidia": {
"models": ["openai/gpt-oss-20b", "nvidia/nemotron-3-super-120b-a12b", "z-ai/glm-5-3-flash"],
"apiKey": apiKeyNvidia,
},
"cloud-ollama": {
"models": ["gpt-oss:20b"],
"apiKey": apiKeyOllama,
}
}
selected = ["nvidia", "nvidia/nemotron-3-super-120b-a12b"]
toolsJsonPath = os.path.join("Tools", "tools.json")
toolsCallJsonPath = os.path.join("Tools", "toolsCall.json")
TOOLS = loadTools(toolsJsonPath)
TOOLS_HANDLERS = loadToolHandlers(toolsCallJsonPath)
prePrompt = "You are a helpful agent that provides detailed answers. Use tools only when the user asks."
agent = AgentHarness(
profiles=profiles,
selected=selected,
tools=TOOLS,
tool_handlers=TOOLS_HANDLERS,
pre_prompt=prePrompt,
worker=agent_worker,
serialized_tools={"runInShell", "runInShellCool"},
approval_tools={"runInShell", "runInShellCool"},
)
def echo(args: list[str], ctx: PanelContext) -> str:
return " ".join(args)
def clear_command(args: list[str], ctx: PanelContext) -> None:
ctx.clear()
def exit_command(args: list[str], ctx: PanelContext) -> None:
ctx.exit()
COMMANDS = [
Command("/echo", "Repeat the arguments", echo),
Command("/clear", "Clear the panel", clear_command),
Command("/exit", "Exit the panel", exit_command),
Command("/compact", "Compact the context window", agent.compact_context),
]
PANEL_CONFS = {
"NAME_TEXT": f"{BOLD + BRIGHT_CYAN}JuCode{RESET}",
"SUBNAME_TEXT": f"{DIM + ITALIC}Powered by Tiny Agents{RESET}",
"INPUT_BOX_UPPER_TEXT": f"{DIM}Type a message | / for commands | Tab to complete{RESET}",
"THINKING_TEXT": "Thinking...",
"MODEL_NAME": agent.model,
"MODEL_PROVIDER": agent.provider,
"MODEL_OPTION": agent.model_option,
"CONTEXT_WINDOW_SIZE": contextWindowSize,
"CONTEXT_WINDOW_MAX": 10000,
"THEME": "default",
"MESSAGE_SPACING": 1,
"STYLE": {
"title": BOLD + BRIGHT_CYAN,
"user": BG_BLUE + WHITE,
"model": WHITE,
"error": RED,
"separator": DIM + BLACK,
"hint": DIM,
"input-field": BG_BLUE + WHITE,
"completion-menu": BG_BRIGHT_BLACK + WHITE,
"completion-menu.completion": WHITE,
"completion-menu.completion.current": BG_BLUE + BOLD + WHITE,
"radio": DIM + WHITE,
"radio-checked": BOLD + BRIGHT_CYAN,
"scrollbar": BG_BLACK,
"scrollbar.button": BG_BLACK + WHITE,
},
"COMMANDS": COMMANDS,
"COMMANDS_LIST": [command.name for command in COMMANDS],
"MODEL_HANDLER": agent.model_handler,
"MODEL_OPTIONS": agent.model_options(),
"MODEL_OPTION_LABELS": agent.model_option_labels(),
"MODEL_SELECT_HANDLER": agent.select_model,
}
panel = initPanel(PANEL_CONFS)
with patch_stdout():
panel.run()
Run the example from the project root so the tool configuration files and Python modules can be found:
uv run --with prompt-toolkit --with python-dotenv main.py
Or, after installing the dependencies with pip:
python main.py
AgentHarness runs model requests and tool execution through the supplied AsyncWorker, saves each exchange to the shared context, and connects tools that accept a write callback to the panel. main.py remains responsible for configuration, application-specific commands, and creating the harness.
Commands include /echo, /clear, /exit, /compact, and /model. Run /model to open the selector above the input box, move with ↑/↓, choose with Enter, or close it with Esc. The options show provider / model; direct selection accepts an option number or its provider::model ID. The harness recreates the client when the provider changes and uses that provider's configured key and URL. For a new provider, add its models and key in profiles and set baseUrl there.
/compact uses the currently selected client and model. /clear clears the visible chat, not the framework's conversation context. Scroll with PgUp/PgDn, or jump to the beginning and end with Ctrl+Home/Ctrl+End.
Panel configuration
Pass a dictionary to initPanel from tinyAgents.panel:
| Key | Purpose |
|---|---|
NAME_TEXT, SUBNAME_TEXT |
Title and subtitle; ANSI formatting is supported. |
INPUT_BOX_UPPER_TEXT |
Extra text beside the scrolling hints. |
THINKING_TEXT |
Activity label; defaults to Thinking.... |
MODEL_NAME |
Current model name displayed in the panel; updated after selection. |
MODEL_PROVIDER |
Current provider displayed with the model when set. |
MODEL_OPTION |
Current selector ID, usually provider::model, used to highlight the selected option. |
MODEL_OPTIONS |
Optional list of model IDs that enables the /model selector. |
MODEL_OPTION_LABELS |
Optional mapping from selector IDs to human-readable labels. |
MODEL_SELECT_HANDLER |
Optional callback receiving the selected option ID. |
MODEL_HANDLER |
Callable receiving (text, ctx) and returning a response or None; synchronous and async handlers are supported. |
COMMANDS |
List of Command(name, description, handler) instances. Command handlers receive (args, ctx) and may be synchronous or async. |
COMMANDS_LIST |
Completion suggestions; defaults to registered command names. |
CONTEXT_WINDOW_SIZE |
Function taking no arguments and returning the current usage; defaults to zero. |
CONTEXT_WINDOW_MAX |
Positive integer used as the meter's maximum; defaults to 10000. |
THEME |
default, dark, light, or ocean; defaults to default. |
MESSAGE_SPACING |
Number of blank lines between messages; defaults to 1, and accepts 0. |
STYLE |
Style overrides for the selected theme. |
PanelContext exposes write(text), clear(), exit(), and async request_approval(tool_name, arguments). Writes and approval prompts from the agent worker are scheduled on the UI loop and chat updates are batched during bursts of output. The spinner refreshes only during activity, and unchanged ANSI lines are cached.
Command handlers are awaited when async; synchronous handlers run in a thread. Async commands are prevented from starting during a model request or another async command. /clear and /exit remain available. Async handlers that use the synchronous SDK must delegate that work to AsyncWorker, as /compact does in the example. dispatch is now async and direct callers must await it.
The activity line stays reserved above the hints. The spinner and activity text appear only while the model handler is running; the context meter remains visible. Its color changes at 70% and 90% usage, and the bar fills completely at or above the maximum.
The size callback and maximum must use the same unit. contextWindowSize counts saved user and response characters, not model tokens, tool logs, or the full API request. The meter updates when the saved context changes. Its maximum is visual configuration: it does not enforce a limit or compact context automatically.
Themes and message styles
Explicit STYLE values override the selected theme. To use a complete preset, omit the corresponding overrides from the example:
PANEL_STYLE = {
"THEME": "ocean",
"MESSAGE_SPACING": 1,
"STYLE": {
"user": "bg:#173f50 #ffffff",
"model": "#daf4f4",
"error": "#ff6b6b bold",
"context-low": "#4caf80",
"context-warning": "#e9b949",
"context-full": "#ef6461 bold",
},
}
Styles accept prompt_toolkit style strings or ANSI constants such as BG_BLUE + WHITE. user applies to sent messages, model to returned model responses, and error to messages beginning with [ERROR]. Tool output remains independent. ANSI formatting embedded in messages is preserved and can override their text color.
The model selector appears directly above the input box and uses the completion-menu background style. Its choices use radio and radio-checked styles. Other style keys include chat-area, hint, separator, input-field, completion-menu.completion, completion-menu.completion.current, scrollbar, and scrollbar.button.
Tool calling
Tools/tools.json defines the function schemas offered to the model. Tools/toolsCall.json maps their names to Python handlers:
{
"hourNow": "Tools.hourNow.hourNow:hourNow",
"runInShell": "Tools.runInShell.runInShell:runInShell",
"runInShellCool": "Tools.runInShell.runInShell:runInShellCool"
}
The included tools are examples and conveniences for applications:
| Tool | Behavior |
|---|---|
hourNow() |
Returns the current UTC date and time under utc. |
runInShell(cmd) |
Collects shell output without displaying it. |
runInShellCool(cmd, write=print) |
Collects shell output and sends the command, output lines, and exit status to the supplied callback. |
Both shell tools return logs and exitCode, combining standard output and standard error. Output is read by line, so subprocess buffering can delay updates. Shell commands execute locally with shell=True.
To add a tool, implement an importable function, add its schema to tools.json, and map its name to module:function in toolsCall.json. Handler keyword arguments must match the schema's parameter names. Handler mappings import Python modules and should come from trusted configuration.
messageToLLMTools lets the model choose tools, executes their handlers, sends results back to the model, and returns (completion, executedTools). Calls in one model response run concurrently. Pass serializedTools={"write_file", "update_database"} to serialize all listed tools against each other, including across simultaneous requests; the AgentHarness constructor accepts the corresponding serialized_tools option. It allows up to five rounds of tool execution and raises RuntimeError("Tool call limit exceeded") if more calls are still requested.
Set approval_tools={"runInShell"} on AgentHarness to require user approval before a tool runs. The panel opens a menu with its arguments; use the arrow keys and Enter, or press y to approve and n/Esc to deny. The example main.py enables approval for both shell tools.
Each execution record includes id, name, arguments, and ok, with result on success or error on failure. Unknown tools, invalid arguments, and handler exceptions become failed tool results. Configuration loading and API errors propagate to the caller.
Tools without the panel
Tools do not import the panel. The output callback is supplied by the application and is not a model parameter or part of the JSON schema:
from Tools.runInShell.runInShell import runInShellCool
result = runInShellCool("echo hello")
AgentHarness connects tools that accept a write parameter to the panel's ctx.write callback when handling a request.
Core without the panel
Import the public API from tinyAgents. The request helpers are async functions and must be awaited:
import asyncio
import os
from tinyAgents import startClient, messageToLLM, addToContextWindow
async def run():
model = "nvidia/nemotron-3-super-120b-a12b"
profiles = {"nvidia": {"apiKey": os.environ["API_KEY_NVIDIA"]}}
client = startClient(profiles, ["nvidia", model])
message = "Hello"
completion = await messageToLLM(
client, model, "You are a helpful assistant.", message
)
response = completion.choices[0].message.content or ""
addToContextWindow(message, response, "user input")
print(response)
asyncio.run(run())
This example reads environment variables directly. The helpers retain synchronous SDK calls internally, so applications with an interactive event loop should run them in a worker, as the panel example does.
Context and API
| API | Purpose |
|---|---|
startClient(profiles, selected) |
Create an OpenAI-compatible client using the selected provider's key and URL. |
messageToLLM(client, model, prePrompt, message) |
Async request without tool calling; returns the SDK completion. |
loadTools(path) |
Load the tools array from JSON. |
loadToolHandlers(path) |
Import and validate configured Python handlers. |
messageToLLMTools(client, model, prePrompt, message, TOOLS, TOOL_HANDLERS, serializedTools=(), onToolStart=None, approvalTools=(), onToolApproval=None) |
Async request with parallel execution, optional serialized tools, and approval callbacks. |
addToContextWindow(message, response, objectType, toolsRunned=[]) |
Save an exchange and optional tool records. |
contextWindow |
Shared in-memory conversation dictionary. |
contextWindowSize() |
Count saved user and response tokens. |
clearContextWindow() |
Clear the saved conversation. |
OldCompactContextWindow(maxSize) |
Remove the oldest exchanges until the size is within a non-negative character limit. |
NewCompactContextWindow(client, model) |
Async request for a summary that replaces the saved conversation; must be awaited. |
prettyResolve(text) |
Format Markdown headings and emphasis for terminal output. |
AgentHarness(...) |
Connect provider/model selection, tool handlers, context, worker, and panel callbacks. |
Requests do not automatically save their results. Pass executedTools to addToContextWindow to retain tool records alongside an exchange. Context is shared within the process and is lost when it exits. Model-based compaction makes an additional API request and may lose details.
Compaction follows the same async calling pattern as messageToLLM:
await NewCompactContextWindow(client, model)
From the panel's UI loop, use the existing worker to keep the synchronous SDK request off the UI thread:
await agent_worker.run(NewCompactContextWindow(client, model))
Project status
Version 0.2.4 adds parallel tool execution, optional tool serialization and approval, and panel responsiveness and presentation updates. The framework remains under active development. See CHANGELOG.md for release notes.
Contributing
Suggestions and contributions are welcome. Open an issue or submit a pull request with a clear description of the change.
Ownership
Developed under KML Research Labs by Kauan Mezavila.
License
Released under the MIT License.
Metadata
Release files for by-tiny-agents 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| by_tiny_agents-0.2.4.tar.gz | 38.3 kB | With provenance | Details for by_tiny_agents-0.2.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| by_tiny_agents-0.2.4-py3-none-any.whl | Python 3 | none | any | Details for by_tiny_agents-0.2.4-py3-none-any.whl |
Total release size: 65.9 kB
Release files / by_tiny_agents-0.2.4.tar.gz
| Download URL | by_tiny_agents-0.2.4.tar.gz |
|---|---|
| Size | 38.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
40469f1b56e8c0b8e1dd491105aeb32f88302d6619c6c73127bdab4b37a7d8db
|
|
BLAKE2b-256 checksum How to use checksums |
13dedd235ef123a7134bd2a5e4928be93f7f6646352b43c855f80267a36ff3cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logRelease files / by_tiny_agents-0.2.4-py3-none-any.whl
| Download URL | by_tiny_agents-0.2.4-py3-none-any.whl |
|---|---|
| Size | 27.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d3eca9fee868f4f6bcda25a34b1632bc6e08e7f2a91bb39e8118ba935678cb34
|
|
BLAKE2b-256 checksum How to use checksums |
91e3a2fcc34fe033db0ca96782e017117fa2548d0e4b05532e3ff6ba368f7998
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logSecurity for by-tiny-agents 0.2.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for by_tiny_agents-0.2.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.