Agent¶
The miminions.agent module is the core reasoning engine — a Minion is an async LLM agent built on pydantic-ai and, by default, OpenRouter. A Minion owns the full agent lifecycle: model selection, a tool registry (plain Python functions, GenericTool objects, and MCP-server tools), optional memory, optional workspace context injection, and the async run() loop.
Import from the subpackage
Always import from miminions.agent — the top-level import miminions does not re-export these symbols.
Features¶
- Async execution — owns its full
async run()reasoning loop - Tool registration — register plain Python functions or
GenericToolobjects as LLM-callable tools - MCP integration — connect to Model Context Protocol servers and load their tools
- Memory — attach any memory backend and get 7 memory tools auto-registered
- Context injection — attach a workspace and the agent builds a dynamic system prompt via
ContextBuilderbefore every turn
Quick Start¶
import asyncio
from miminions.agent import create_minion
async def main():
agent = create_minion("MyAgent")
def add(a: int, b: int) -> int:
return a + b
agent.register_tool("add", "Add two numbers", add)
reply = await agent.run("What is 3 + 7?")
print(reply)
asyncio.run(main())
Set OPENROUTER_API_KEY
The default provider is OpenRouter. run() needs a working backend, so export OPENROUTER_API_KEY first — otherwise the call fails with an auth/network error. For offline runs (tests, examples), use provider="test" (see below).
Model & Provider Selection¶
create_minion defaults to OpenRouter's free openai/gpt-oss-20b:free model. Switch providers with provider=, or pass a fully-built pydantic-ai model with model=.
from miminions.agent import create_minion
# Default: OpenRouter free model — needs OPENROUTER_API_KEY
agent = create_minion("MyAgent")
# Anthropic, OpenAI, or Gemini (each needs its own API key in the env)
agent = create_minion("MyAgent", provider="anthropic")
provider |
Default model | Required env var |
|---|---|---|
"openrouter" (default) |
openai/gpt-oss-20b:free |
OPENROUTER_API_KEY |
"openai" |
gpt-4o |
OpenAI key |
"anthropic" |
claude-3-5-sonnet-latest |
Anthropic key |
"gemini" |
gemini-1.5-flash |
Gemini key |
"test" |
TestModel (offline) |
— |
Switching models later
Call agent.set_model(model) to swap the underlying pydantic-ai model at runtime — it rebuilds the internal agent immediately.
Registering Tools¶
A Minion can run two flavours of tool. Use register_tool for plain functions, or add_tool for a GenericTool.
from miminions.agent import create_minion
from miminions.tools import tool
agent = create_minion("MyAgent")
# 1. Plain function — schema inferred from the signature
def multiply(a: int, b: int) -> int:
return a * b
agent.register_tool("multiply", "Multiply two numbers", multiply)
# 2. A GenericTool built with @tool
@tool(name="greet", description="Greet a user by name")
def greet(name: str) -> str:
return f"Hello, {name}!"
agent.add_tool(greet)
print(agent.list_tools()) # ['multiply', 'greet']
See Tools for @tool, create_tool, and the GenericTool API.
Attaching Memory¶
Pass memory= at construction, or call set_memory(...) later. The moment memory is attached, 7 tools auto-register so the LLM can read and write memory on its own:
memory_store, memory_recall, memory_update, memory_delete, memory_get, memory_list, and ingest_document (chunked PDF/text ingestion).
from miminions.agent import create_minion
from miminions.memory.sqlite import SQLiteMemory # requires the [sqlite] extra
memory = SQLiteMemory(db_path=":memory:")
agent = create_minion("MyAgent", memory=memory)
# Direct (non-LLM) helpers — both require memory to be attached
entry_id = agent.store_knowledge("MiMinions runs on pydantic-ai.")
hits = agent.recall_knowledge("what does MiMinions run on?", top_k=3)
print(hits[0]["text"]) # result dicts use the key "meta", not "metadata"
Note
store_knowledge / recall_knowledge raise ValueError("No memory attached") if no backend is set. SQLiteMemory lives in miminions.memory.sqlite (not the package top level) and needs pip install "miminions[sqlite]". See Memory for the full backend reference.
Attaching a Workspace¶
Attach a workspace with set_context(workspace, root_path). WorkspaceManager requires a config directory and you resolve a single workspace with resolve_workspace — there is no get_workspace method.
from pathlib import Path
from miminions.agent import create_minion
from miminions.core.workspace import WorkspaceManager, resolve_workspace
config_dir = Path.home() / ".miminions"
mgr = WorkspaceManager(config_dir)
# resolve by exact id, id prefix, or exact name
ws = resolve_workspace(mgr.load_workspaces(), "default")
agent = create_minion("MyAgent")
agent.set_context(ws, root_path=config_dir / "workspaces" / f"ws_{ws.id}")
Once context is set, a @agent.system_prompt callback runs ContextBuilder().build(workspace, root_path) on every run(), injecting identity, prompt files, memory, the workspace graph summary, and a skills index into the system prompt. Call set_context before the first run() for it to take effect.
Builder signature
ContextBuilder().build(workspace, root_path) takes the workspace and root path as arguments. See Context Builder for the emitted sections.
Loading MCP Tools¶
Connect to a Model Context Protocol server, then load its tools. The flow is two steps — connect, then load. (MCP needs the optional mcp package.)
from mcp import StdioServerParameters
from miminions.agent import create_minion
agent = create_minion("MyAgent")
await agent.connect_mcp_server(
"filesystem",
StdioServerParameters(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
),
)
# Register every tool the server exposes
await agent.load_tools_from_mcp_server("filesystem")
print(agent.list_tools())
# Close MCP connections when done
await agent.cleanup()
Tip
await agent.load_tools_from_all_servers() loads tools from every connected server in one call.
Executing Tools Directly vs. run()¶
There are three ways to invoke a tool. Pick by how you want errors and results surfaced.
| Method | Returns | On error |
|---|---|---|
await run(prompt) |
the LLM's reply (the model decides which tools to call) | re-raises network/API errors |
execute(name, arguments=None, **kwargs) |
a ToolExecutionResult |
never raises — failure is captured on the result |
execute_tool(name, **kwargs) |
the tool's raw return value | raises ValueError / RuntimeError |
# Let the LLM reason and call tools as needed
reply = await agent.run("Multiply 6 by 7")
# Structured result — inspect .status / .error / .result
from miminions.tools.schemas import ExecutionStatus
res = agent.execute("multiply", {"a": 6, "b": 7})
print(res.result if res.status == ExecutionStatus.SUCCESS else res.error)
# Raw result — raises on missing tool or execution error
value = agent.execute_tool("multiply", a=6, b=7) # -> 42
Async tools
execute / execute_tool are synchronous and reject coroutine tools. For async tools use await execute_async(...) (structured) or await execute_tool_async(...) (raw).
Multi-turn Conversations¶
run() stores the full message list on agent._last_messages after each call. Pass it back as message_history to continue a conversation within a session.
reply1 = await agent.run("My name is Asher.")
reply2 = await agent.run("What is my name?", message_history=agent._last_messages)
API Reference¶
create_minion¶
create_minion(
name: str,
description: str = "",
memory: BaseMemory | None = None,
model: Any | None = None,
provider: str = "openrouter",
) -> Minion
Factory that builds a configured Minion. When model is None, provider drives model selection (default OpenRouter free model). This is the primary entry point.
Minion methods¶
| Method | Description |
|---|---|
async run(prompt, message_history=None) -> str |
Send a prompt; the model reasons and calls tools. Returns the reply. |
register_tool(name, description, func, schema=None) -> ToolDefinition |
Register a Python function as a tool (schema inferred if omitted). |
add_tool(tool: GenericTool) -> ToolDefinition |
Register a GenericTool. |
unregister_tool(name) -> bool |
Remove a tool by name. |
list_tools() -> list[str] |
List registered tool names. |
search_tools(query) -> list[str] |
Case-insensitive search over tool names/descriptions. |
execute(name, arguments=None, **kwargs) -> ToolExecutionResult |
Run a tool synchronously; never raises. |
async execute_async(name, arguments=None, **kwargs) -> ToolExecutionResult |
Async variant of execute; awaits coroutine tools. |
execute_tool(name, **kwargs) -> Any |
Run a tool and return its raw result; raises on error. |
async execute_tool_async(name, **kwargs) -> Any |
Async raw-result variant; raises on error. |
set_memory(memory) -> None |
Attach a memory backend and auto-register the 7 memory tools. |
store_knowledge(text, metadata=None) -> str |
Store text in memory; raises ValueError if no memory attached. |
recall_knowledge(query, top_k=5) -> list[dict] |
Recall from memory; raises ValueError if no memory attached. |
set_context(workspace, root_path) -> None |
Attach workspace context for ContextBuilder injection on every run. |
set_model(model) -> None |
Swap the pydantic-ai model and rebuild the agent. |
async connect_mcp_server(name, server_params) -> None |
Connect to an MCP server (StdioServerParameters). |
async load_tools_from_mcp_server(name) -> list[ToolDefinition] |
Register all tools from a connected server. |
async load_tools_from_all_servers() -> list[ToolDefinition] |
Load tools from every connected server. |
async cleanup(rebuild=True) -> None |
Close MCP connections (and rebuild the internal agent). |
get_state() -> AgentState |
Snapshot: config, tool count, memory flag, connected servers. |
Properties¶
| Property | Returns |
|---|---|
name |
The agent name (str). |
description |
The agent description (str). |
memory |
The attached BaseMemory or None. |
See Also¶
- Memory — vector and markdown memory backends
- Context Builder — what gets injected into the system prompt
- Tools —
@tool,create_tool, andGenericTool - Workspaces — nodes, rules, and on-disk layout