# Quick Start ## Getting Started Yoker provides an interactive chat interface with Ollama and tool calling capabilities. **Architecture**: Yoker uses an event-driven, library-first design. The Agent emits events (thinking chunks, content, tool calls) that handlers subscribe to. This makes the library usable in headless, web, and GUI contexts. ```{image} _static/architecture-diagram.svg :alt: Architecture Diagram ``` ### Prerequisites - Python 3.10 or higher - [Ollama](https://ollama.ai) running with at least one model ### Install ```bash pip install -e . ``` Or from PyPI: ```bash pip install yoker ``` ### Run ```bash python -m yoker ``` Or with a configuration file: ```bash python -m yoker --config yoker.toml ``` ### Library Usage (Headless) ```python import asyncio from yoker import Agent from yoker.events import Event, ContentChunkEvent # Create a custom async event handler (recommended) class MyAsyncHandler: """Async event handler for processing events. Async handlers are recommended for most use cases, especially when performing I/O operations like writing to files, making network calls, or interacting with databases. """ async def __call__(self, event: Event) -> None: if isinstance(event, ContentChunkEvent): print(event.text, end='', flush=True) # Sync handlers are also supported for simple, fast operations class MySyncHandler: """Sync handler for simple operations (no I/O). Sync handlers are supported for cases where the handler just performs fast, non-blocking operations like logging or counters. """ def __call__(self, event: Event) -> None: if isinstance(event, ContentChunkEvent): print(event.text, end='', flush=True) async def main(): # Create agent and attach async handler agent = Agent(model="llama3.2") agent.add_event_handler(MyAsyncHandler()) # Use the agent programmatically (all methods are async) await agent.begin_session() await agent.process("What is 2+2?") await agent.end_session() # Run the async main function asyncio.run(main()) ``` ### Async Model Yoker is built with an async-first architecture: - **All Agent methods are async**: `begin_session()`, `process()`, and `end_session()` are all coroutines - **Event handlers can be async**: Handlers receive events in the same asyncio task as `process()` - **No thread pool bridging needed**: Async handlers can directly `await` I/O operations ```python class DatabaseHandler: """Example async handler writing to a database.""" def __init__(self, db_connection): self.db = db_connection async def __call__(self, event: Event) -> None: if isinstance(event, ContentEndEvent): # Direct await - no run_coroutine_threadsafe needed! await self.db.save_response(event.total_length) async def main(): agent = Agent(model="llama3.2") agent.add_event_handler(DatabaseHandler(db)) await agent.begin_session() await agent.process("Hello") # Handler runs in same event loop await agent.end_session() ``` **Threading Model:** | Context | Where handlers run | |---------|---------------------| | `await agent.process()` | Same asyncio task | | Interactive CLI | Main asyncio event loop | | Web server (FastAPI) | Request handler's event loop | Handlers run synchronously within the asyncio event loop. For long-running operations, use async handlers that yield control (e.g., `await asyncio.sleep(0)`) or offload to a separate executor. ### Interactive Session ``` Yoker v0.3.0 - Using model: glm-5:cloud Thinking mode: enabled (use /think on|off to toggle) Type /help for available commands. Press Ctrl+D (or Ctrl+Z on Windows) to quit. > /help Available commands: /help - Show available commands /think - Enable/disable thinking mode: /think [on|off] Type a message without / prefix to chat with the LLM. > What's in the README.md file? I'll read the README.md file for you. The README.md file describes **Yoker**, a Python-based agent harness... > ^D Goodbye! ``` ### Interactive Input Features The session supports: | Feature | How to use | |---------|------------| | Multiline input | `Esc+Enter` adds newlines, `Enter` submits | | Command history | `Up`/`Down` arrows navigate previous messages | | History search | `Ctrl+R` searches through history | | Mouse support | Click to position cursor | ### Slash Commands | Command | Description | |---------|-------------| | `/help` | Show available commands | | `/think on\|off` | Enable/disable LLM thinking trace | ### Thinking Mode When enabled, the LLM shows its reasoning process in gray: ``` [Thinking] Let me analyze this request... I should check the file structure first... [Response] Based on my analysis, here's what I found... ``` ### Command Line Options ```bash python -m yoker --help Options: -c, --config PATH Path to configuration file (default: yoker.toml) -m, --model MODEL Model to use (overrides config) -a, --agent PATH Path to agent definition file (Markdown with frontmatter) ``` ### Session Persistence Yoker supports session persistence for resuming conversations: ```bash # Start a session with persistence python -m yoker --persist # Resume a previous session python -m yoker --resume ``` When using `--persist`, the session is saved after each turn. Use `--resume` to continue a previous session with full context restored. **Programmatic usage:** ```python import asyncio from pathlib import Path from yoker.agent import Agent from yoker.context import BasicPersistenceContextManager async def main(): # Create context manager for persistence context = BasicPersistenceContextManager( storage_path=Path(".yoker/sessions"), session_id="my-session" ) # Create agent with context agent = Agent(context_manager=context) # Use the agent (all methods are async) await agent.begin_session() await agent.process("What is 2+2?") await agent.end_session() # Later, resume the session context = BasicPersistenceContextManager( storage_path=Path(".yoker/sessions"), session_id="my-session" ) agent = Agent(context_manager=context) # Context is automatically loaded asyncio.run(main()) ``` ## Tools Yoker provides several tools for file operations and subagent spawning: | Tool | Description | |------|-------------| | `read` | Read file contents with guardrails | | `list` | Directory listing with pattern filtering | | `write` | Write files with overwrite protection | | `update` | Edit existing files with replace/insert/delete | | `search` | Search file contents or filenames | | `existence` | Check if files or folders exist | | `mkdir` | Create directories with parent creation | | `git` | Git operations with permission-controlled commit/push | | `agent` | Spawn subagents with isolated context | ### Agent Tool The `agent` tool allows spawning subagents for specialized tasks: ```{image} _static/demo-agent-tool.svg :alt: Agent Tool Demo ``` ``` > Use the agent tool to spawn a researcher agent to find all TODO comments [Tool Call] agent(agent_path="examples/agents/researcher.md", prompt="Find all TODO comments") The researcher agent found 15 TODO items across the codebase... ``` Key features: - **Isolated context**: Subagents start with fresh context - **Recursion limits**: Maximum depth of 3 by default - **Timeout**: 5-minute default execution limit - **Tool filtering**: Subagents use only their defined tools ## Agent Definitions Yoker supports loading agent definitions from Markdown files with YAML frontmatter. This allows you to define custom system prompts and tool availability. ### Agent Definition Format Create a file like `examples/agents/researcher.md`: ```markdown --- name: researcher description: Research assistant that searches and reads files tools: List, Read, Search color: blue --- # Researcher Agent You are a research assistant specialized in finding and analyzing information. ## Workflow 1. Use Search to find relevant files 2. Use Read to examine file contents 3. Compile findings into a structured report ``` ### Using Agent Definitions ```bash # Load an agent definition python -m yoker --agent examples/agents/researcher.md ``` Programmatically: ```python from yoker.agent import Agent from yoker.agents import load_agent_definition, validate_agent_definition from yoker.config import load_config # Load configuration and agent config = load_config("yoker.toml") agent_def = load_agent_definition("agents/researcher.md") # Validate agent tools against config warnings = validate_agent_definition(agent_def, config.tools) # Create agent with definition agent = Agent(config=config, agent_definition=agent_def) # agent.messages[0] now contains the system prompt from the Markdown body ``` ### Example Agents | Agent | File | Description | |-------|------|-------------| | Main | `examples/agents/main.md` | Default assistant with read-only tools | | Researcher | `examples/agents/researcher.md` | Research assistant with search capabilities | | Markdown | `examples/agents/markdown.md` | Formats all responses as structured Markdown | ## Configuration ### Configuration File Create a `yoker.toml` file in your project directory: ```toml [harness] name = "my-yoke" log_level = "INFO" [backend] provider = "ollama" [backend.ollama] base_url = "http://localhost:11434" model = "llama3.2:latest" timeout_seconds = 60 [backend.ollama.parameters] temperature = 0.7 top_p = 0.9 [tools.read] enabled = true allowed_extensions = [".txt", ".md", ".py", ".json"] [logging] format = "json" include_tool_calls = true ``` See `examples/yoker.toml` for the full configuration reference. ### Environment Variables Yoker auto-discovers configuration files in this order: 1. `./yoker.toml` (current directory) 2. `~/.yoker.toml` (user home directory) 3. Built-in defaults You can also specify an explicit config file: ```bash python -m yoker --config path/to/config.toml ``` ## Available Tools | Tool | Purpose | |------|---------| | `read` | Read file contents | | `list` | List directory contents with pattern filtering | | `write` | Write content to files with overwrite protection | | `update` | Edit existing files with replace, insert, and delete | | `search` | Search file contents with regex or filenames with glob | | `git` | Git operations (status, log, diff, branch, show) | ## Tool Examples ### Read Tool ![Read Tool Demo](_static/demo-read-tool.svg) Reading the first 3 lines of `README.md`. ### List Tool ![List Tool Demo](_static/demo-list-tool.svg) Listing files matching `CLAUDE*` in the current directory. ### Write Tool ![Write Tool Demo](_static/demo-write-tool.svg) Writing "Hello from Yoker!" to `/tmp/yoker-demo.txt` and reading it back. ### Update Tool ![Update Tool Demo](_static/demo-update-tool.svg) Replacing text in an existing file with the update tool. ### Search Tool ![Search Tool Demo](_static/demo-search-tool.svg) Searching for content with regex patterns or filenames with glob patterns. ### Git Tool ![Git Tool Demo](_static/demo-git-tool.svg) Running Git operations (status, log, diff) on a repository. ### Commands ![Commands Demo](_static/demo-commands.svg) Using `/help`, `/think on`, and `/think off` slash commands. ### Thinking Mode ![Thinking Mode Demo](_static/demo-thinking.svg) Thinking mode enabled shows the LLM reasoning process in gray before the response. --- ## Next Steps - {doc}`installation` - Detailed installation guide - [Architecture](https://github.com/christophevg/yoker/blob/master/analysis/architecture.md) - System design