Skip to main content

Getting Started Guide

Use Agent when you want the normal Python entry point.

The wrapper exposes:

  • Agent
  • AgentLoopRequest
  • AgentLoopResult
  • AgentRunResult
  • ExecutionStep
  • MemoryBackend
  • MemoryEntry
  • MemoryKind
  • MemoryModule
  • RunContext
  • Tool
  • ToolRegistry

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 backend
  • deps_type: optional dependency type for tools that receive context
  • instructions: system prompt preamble
  • agentic_loop: prompt-level loop instructions embedded into the system prompt
  • agent_loop_handler: Python callback that overrides the default runtime loop
  • name: agent name
  • max_iterations: backend iteration limit
  • workspace_home: optional workspace root path
  • tools: optional list of prebuilt Tool instances
  • tool_registry: optional reusable ToolRegistry
  • memories: optional list of prebuilt MemoryModule instances

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_id
  • user_message
  • system_prompt
  • messages
  • tools
  • agent_dir
  • workspace_dir
  • sessions_dir
  • model
  • max_iterations
  • deps

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:

  • output
  • steps

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:

  • str
  • int
  • float
  • bool
  • list[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))