Getting Started Guide
Use Agent when you want the normal Python entry point.
The wrapper exposes:
AgentAgentLoopRequestAgentLoopResultAgentRunResultExecutionStepMemoryBackendMemoryEntryMemoryKindMemoryModuleRunContextToolToolRegistry
Create an agent
from enki_py import Agent
agent = Agent(
"ollama::qwen3.5:latest",
deps_type=str,
instructions="Use the player's name in the answer.",
name="Dice Game",
max_iterations=20,
workspace_home=None,
)
Common constructor parameters:
model: model identifier passed through to the backenddeps_type: optional dependency type for tools that receive contextinstructions: system prompt preambleagentic_loop: prompt-level loop instructions embedded into the system promptagent_loop_handler: Python callback that overrides the default runtime loopname: agent namemax_iterations: backend iteration limitworkspace_home: optional workspace root pathtools: optional list of prebuiltToolinstancestool_registry: optional reusableToolRegistrymemories: optional list of prebuiltMemoryModuleinstances
Register memory
Use MemoryBackend as the extension point for custom memory. Implement the abstract methods, then pass backend.as_memory_module() to the agent.
Custom memory methods may be either normal functions or async def coroutine functions. The wrapper accepts both styles.
See:
Register tools
There are two decorators:
@agent.tool_plain
Use for tools with only explicit JSON arguments.
@agent.tool_plain
def roll_dice() -> str:
"""Roll a six-sided die and return the result."""
return "4"
@agent.tool
Use when the first argument is a RunContext.
from enki_py import RunContext
@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
"""Get the player's name."""
return ctx.deps
Reuse a ToolRegistry
Use ToolRegistry when you want to register tools once and attach them to agents later:
from enki_py import Agent, ToolRegistry
registry = ToolRegistry()
@registry.tool_plain
def lookup_release_note(feature: str) -> str:
return f"release-note:{feature}"
agent = Agent("ollama::qwen3.5:latest")
agent.connect_tool_registry(registry)
You can also pass tool_registry=registry to Agent(...) during construction.
Customize the loop
There are two different customization levels.
Prompt-level customization
Use agentic_loop= when you want to keep the normal Rust runtime loop but replace the default loop instructions that the model sees:
from enki_py import Agent
agent = Agent(
"ollama::qwen3.5:latest",
instructions="Answer clearly and keep responses short.",
agentic_loop=(
"1. Understand the request.\n"
"2. Decide whether a tool is needed.\n"
"3. Summarize observations.\n"
"4. Return the final answer."
),
)
Python-defined loop override
Use agent_loop_handler= when you want Python to own the turn-by-turn control flow:
from enki_py import Agent, AgentLoopRequest, AgentLoopResult, ExecutionStep
def custom_loop(request: AgentLoopRequest[None]) -> AgentLoopResult:
return AgentLoopResult(
output=f"Handled in Python for: {request.user_message}",
steps=[
ExecutionStep(
index=1,
phase="Custom",
kind="final",
detail="Returned a final answer from Python",
)
],
)
agent = Agent(
"ollama::qwen3.5:latest",
instructions="Answer clearly and keep responses short.",
agent_loop_handler=custom_loop,
)
The loop request includes:
session_iduser_messagesystem_promptmessagestoolsagent_dirworkspace_dirsessions_dirmodelmax_iterationsdeps
You can also update the loop handler after construction with:
agent.set_agent_loop_handler(handler)agent.clear_agent_loop_handler()
Running the agent
Async:
result = await agent.run("My guess is 4", deps="Anne")
print(result.output)
Sync:
result = agent.run_sync("My guess is 4", deps="Anne")
print(result.output)
run_sync() uses asyncio.run() when no loop is active, and falls back to a background thread if a loop is already running.
Inspect execution steps
Every run returns an AgentRunResult with:
outputsteps
You can also observe steps during execution with on_step:
from enki_py import Agent, ExecutionStep
agent = Agent("ollama::qwen3.5:latest")
def log_step(step: ExecutionStep) -> None:
print(f"[{step.index}] {step.phase} -> {step.kind}: {step.detail}")
result = agent.run_sync("List the main runtime components.", on_step=log_step)
print(result.output)
Tool schemas
Tool.from_function() inspects Python type annotations and builds a JSON schema automatically for:
strintfloatboollist[T]tuple[T]dict[str, T]- optional values such as
str | None
Example generated schema:
@agent.tool_plain
def format_score(total: int, lucky: bool = False) -> str:
return f"{total}:{lucky}"
Produces:
{
"type": "object",
"properties": {
"total": { "type": "integer" },
"lucky": { "type": "boolean" }
},
"additionalProperties": false,
"required": ["total"]
}
Register tool objects directly
If you do not want decorators, create Tool instances directly:
from enki_py import Agent, Tool
agent = Agent("test-model")
def format_score(total: int) -> str:
return f"score:{total}"
agent.register_tool(Tool.from_function(format_score, uses_context=False))