A Python agent harness with configurable tools and guardrails - one who yokes agents together
Project description
Yoker
A Python agent harness with configurable tools, guardrails, and multi-provider LLM backend integration.
Installation
pip install yoker
Quick Start
Run Yoker interactively (default):
python -m yoker
Or with an agent definition:
python -m yoker --agents-definition examples/agents/researcher.md
Example session:
Usage Modes
Yoker supports three ways to run: interactive CLI, batch/non-interactive, and as a library.
Interactive Mode
Interactive mode is the default. It provides a rich terminal UI with multiline input, command history, streaming output, and tool call display.
python -m yoker
# With an agent definition
python -m yoker --agents-definition examples/agents/researcher.md
# Hide tool calls and statistics
python -m yoker --ui-mode interactive
See Interactive Input and Slash Commands for the available keyboard shortcuts and commands.
Batch Mode
Batch mode reads input from stdin and writes response content to stdout. Thinking, tool calls, errors, and statistics are written to stderr. This makes Yoker usable in pipelines and scripts.
# Single prompt
python -m yoker --ui-mode batch
Hello, how can you help me?
^D
# Pipe input
printf "Hello\nWhat is 2+2?\n" | python -m yoker --ui-mode batch
# Show thinking and tool calls on stderr
printf "Hello\n" | python -m yoker --ui-mode batch --ui-show-thinking --ui-show-tool-calls
Batch mode options:
| Flag | Effect |
|---|---|
--ui-mode batch |
Enable batch mode |
--ui-show-thinking |
Print thinking/trace output to stderr |
--ui-show-tool-calls |
Print tool call information to stderr |
--ui-show-stats |
Print turn statistics to stderr |
Library Usage
Yoker is designed to be embedded as a library. The top-level yoker package
exposes a thin Pythonic facade (MBI-003) as the recommended high-level API,
plus a lower-level Agent + UIBridge path for full control.
Python API (recommended)
| Function | Use |
|---|---|
yoker.process(prompt, **kwargs) |
One-shot turn; returns the response string. |
yoker.do(skill_name, prompt, args="", **kwargs) |
One-shot skill invocation. |
yoker.agent(**kwargs) -> Agent |
Builder that returns a reusable Agent. |
yoker.session(id=..., *, persist=True, fresh=False, **kwargs) |
Async context manager yielding a multi-turn Session with context persistence. |
yoker.run_sync(coro) |
Wraps asyncio.run for synchronous callers (scripts, notebooks, REPLs). |
import asyncio
import yoker
async def main():
# One-shot turn.
answer = await yoker.process("What is 2+2?")
print(answer)
# Reusable agent with a tool whitelist and event handler.
reviewer = yoker.agent(
model="qwen3.5:cloud",
system_prompt="You are a security-focused code reviewer. Cite file:line.",
tools=["read", "search", "list"],
)
report = await reviewer.process("Review src/yoker/plugins/security.py for vulnerabilities.")
print(report)
# Multi-turn conversation with automatic context persistence.
async with yoker.session(id="refactor-auth") as session:
await session.agent.process("Read src/auth.py and identify the main responsibilities.")
await session.agent.process("Suggest a refactor that splits authentication from session management.")
asyncio.run(main())
Sync callers (scripts, notebooks):
import yoker
answer = yoker.run_sync(yoker.process("What files are in the current directory?"))
print(answer)
See examples/python_api/ for the full set of facade examples (one_shot.py,
agent_builder.py, session.py, run_skill.py, workflow.py,
event_handling.py, sync_usage.py) and the Quick start docs.
Low-level event-driven API (advanced)
The Agent class emits events; your application implements a UIHandler (or uses the built-in handlers) and wires events through UIBridge.
import asyncio
from yoker import Agent
from yoker.config import get_yoker_config
from yoker.ui import BatchUIHandler, UIBridge
async def main():
config = get_yoker_config(cli=False)
agent = Agent(config=config)
ui = BatchUIHandler(show_thinking=True, show_tool_calls=True)
bridge = UIBridge(ui)
agent.on_event(bridge)
await ui.start(agent)
try:
response = await agent.process("Hello, how can you help me?")
print(response)
finally:
await ui.shutdown("complete")
asyncio.run(main())
See the examples/ directory for more complete examples:
examples/batch_mode.py- Batch mode with predefined messagesexamples/library_usage.py- Using Yoker as a library without the CLIexamples/custom_handler.py- Implementing a customUIHandlerexamples/research_workflow.py- Running a researcher agent programmatically
Plugins
Yoker can load tools, skills, and agents from external Python packages via the plugin system.
Quick Start
Plugins are disabled by default for security. Enable them in your configuration:
# Create yoker.toml
cat > yoker.toml << EOF
[plugins]
enabled = true
EOF
# Secure the configuration file
chmod 600 yoker.toml
Load plugins with --with:
# Using uvx (recommended)
uvx --with pkgq yoker --with pkgq
# Or install first
pip install pkgq
python -m yoker --with pkgq
Security Workflow
When you load a plugin for the first time, Yoker shows a confirmation dialog with the plugin's components (tools, skills, agents). Review them carefully—plugins can execute arbitrary code.
After accepting, Yoker displays instructions to trust the plugin permanently:
[plugins.trusted]
pkgq = true
Using Plugin Components
Verify loaded components with:
> /skills # List all skills (including plugin skills)
> /tools # List all tools (including plugin tools)
> /pkgq:create # Invoke a plugin skill directly
Available Plugins
- pkgq - Package documentation tools (PyPI:
pip install pkgq)pkgq:findtool - Find Python package documentationpkgq:createskill - Generate PACKAGE.md for a projectpkgq:updateskill - Update documentation for new versions
Developing Plugins
See examples/plugins/demo/README.md for a complete plugin development guide.
For comprehensive plugin documentation including security best practices, configuration reference, and troubleshooting, see docs/plugins.md.
Why Yoker?
Yoker fills a unique gap in the coding agent ecosystem: a library-first, transparent agent harness designed for developers who want full control, visibility, and simplicity.
Key Differentiators:
- Library-first - Embed in your applications, not locked into a CLI
- LLM-neutral - Choose your provider, your model, your cost model
- No hidden manipulation - All prompts visible, editable, configurable
- Static permissions - Deterministic boundaries, not runtime prompts
- Full transparency - Event-driven, everything inspectable
See docs/rationale.md for the full rationale and comparison with other solutions.
Features
Current Features:
- Chat loop - Interactive conversation with any configured provider
- Multi-provider backends - Ollama (native SDK), OpenAI, Anthropic, Google Gemini, and 100+ providers via LiteLLM
- Bootstrap wizard - Interactive first-run setup that writes
~/.yoker.tomlfor you - Tool calling - Structured tool execution with parameters
-
readtool - Read file contents with guardrails -
listtool - Directory listing with pattern filtering -
writetool - Write files with overwrite protection -
updatetool - Edit existing files with replace, insert, and delete operations -
searchtool - Search file contents with regex or filenames with glob -
existencetool - Check if files or folders exist with security hardening -
mkdirtool - Create directories with recursive parent creation and depth limits -
gittool - Git operations (status, log, diff, branch, show) with permission-controlled commit/push -
websearchtool - Web search with SSRF protection, domain filtering, and rate limiting -
webfetchtool - Fetch web content with SSRF protection, URL validation, and size limits -
agenttool - Spawn subagents with isolated context and recursion limits -
skilltool - Invoke skills dynamically by name with full content loading - Slash commands - Built-in commands:
/help,/think on|off|silent,/skills,/context,/tools,/agents - Thinking mode - LLM reasoning trace with gray output (on/off/silent)
- Streaming - Real-time token streaming from any provider
- Configuration - TOML-based configuration system via Clevis
- Agent definitions - Load agents from Markdown files with YAML frontmatter
- Package plugins - Load tools, skills, and agents from Python packages with
--with - Multiline input -
Esc+Enterfor newlines,Enterto submit - Rich output - Styled terminal output with Rich
- Event-driven architecture - Library-first design with event emission
- Context persistence - Session resumption with JSONL storage
- Event logging - Full session replay capability
- Demo scripts - Generate documentation screenshots from Markdown scripts
- Schema-driven guardrails - Tool parameters are annotated with
yoker.tools.annotationsmarkers (Path,Url,Query,Text); the harness strips the metadata before sending schemas to the model and dispatches the matching guardrail at execution time - Permissions - Static TOML-based access control
- Secure API key handling - Masked input during bootstrap, config files written with
chmod 600
Planned Features:
- Multi-agent orchestration - Run coordinated agent teams
- Keyring integration - Store API keys in the OS keychain instead of config files (TODO S.1)
- Tool timing metrics - Performance tracking
- Token usage tracking - Cost monitoring
- Tool result caching - Reduce redundant calls
- Parallel tool execution - Concurrent read operations
Interactive Input
The interactive session supports:
- Multiline input: Press
Esc+Enterto add newlines,Enterto submit - Command history: Up/Down arrows navigate previous messages
- History search:
Ctrl+Rto search through history - Keyboard navigation: Arrow keys, Ctrl+A/E for cursor positioning
- Text selection: Click and drag to select output, copy with Ctrl+Shift+C or Cmd+C
Slash Commands
| Command | Description |
|---|---|
/help |
Show available commands |
/think on|off |
Enable/disable LLM thinking trace |
/skills |
List available skills |
/context |
Show current conversation context |
/tools |
List available tools and their availability |
/agents |
Show loaded and available agents |
Thinking Mode
When thinking is enabled, the LLM shows its reasoning process:
[Thinking]
Let me analyze this step by step...
First, I need to understand the file structure...
[Response]
Based on my analysis, here's what I found...
Demo Session Script
Generate terminal screenshots for documentation from Markdown script files:
# Run default demo script (demos/session.md)
python scripts/demo_session.py
# Run a specific demo script
python scripts/demo_session.py --script demos/list-tool.md
# Run all demo scripts in a directory
python scripts/demo_session.py --scripts-dir demos/
# Real LLM + log conversation for replay
python scripts/demo_session.py --script demos/session.md --log
# Replay from log (no LLM calls)
python scripts/demo_session.py --script demos/session.md --replay
# With an agent definition
python scripts/demo_session.py --script demos/session.md --agent examples/agents/markdown.md
Configuration
Yoker auto-discovers configuration files:
./yoker.toml(current directory)~/.yoker.toml(user home directory)- Built-in defaults
# Zero-configuration startup - uses auto-discovered config
python -m yoker
Or create a yoker.toml file for explicit configuration. Yoker supports multiple
providers — Ollama (native SDK), and OpenAI, Anthropic, Google Gemini, plus any
LiteLLM-supported provider (e.g. Groq, Cohere, Azure, Mistral) via the LiteLLM backend:
[harness]
name = "my-yoke"
[logging]
level = "INFO"
[backend]
provider = "ollama" # or "openai", "anthropic", "gemini", or any litellm provider
[backend.ollama]
base_url = "http://localhost:11434"
api_key = "" # Optional API key for authenticated Ollama endpoints
model = "qwen3.5:cloud"
[agents]
definition = "./agents/researcher.md" # Optional: agent definition file
[tools.read]
enabled = true
allowed_extensions = [".txt", ".md", ".py"]
Example for OpenAI:
[backend]
provider = "openai"
[backend.openai]
api_key = "${OPENAI_API_KEY}" # Interpolated from environment variables
model = "gpt-4o-mini"
The bootstrap wizard (run python -m yoker with no config present) writes
~/.yoker.toml for you interactively, including masked API key entry and
chmod 600 file permissions. See Model Catalog for the
curated model lists per provider.
See examples/yoker.toml for the full configuration reference.
Architecture
Yoker uses an event-driven architecture for library-first design. The Agent emits events; the UI layer receives them through UIBridge and decides how to present them.
Agent layer (yoker.core): Configuration, context management, tool execution, and event emission. It has no terminal or presentation logic.
Backend layer (yoker.backends): Provider-neutral streaming chat backend. OllamaBackend uses the native Ollama SDK; LitellmBackend unifies OpenAI, Anthropic, Gemini, and 100+ LiteLLM-supported providers. The ModelBackend Protocol normalizes streaming into provider-agnostic ChatChunk events.
UI layer (yoker.ui): Implements the UIHandler protocol. Built-in implementations:
InteractiveUIHandler- Rich terminal UI with streaming outputBatchUIHandler- stdin/stdout/stderr for scripts and pipelines
Bridge (yoker.ui.UIBridge): Converts agent events into UIHandler method calls so the agent stays independent of presentation details.
Event Types: Turn (start/end), Thinking (start/chunk/end), Content (start/chunk/end), Tool (call/result/content), Command
Providers
Yoker supports multiple LLM providers through a dual backend architecture:
| Provider | Backend | API key required | Free tier |
|---|---|---|---|
| Ollama | Native Ollama SDK | No (app path) or yes (API key) | Yes |
| OpenAI | LiteLLM | Yes | No |
| Anthropic | LiteLLM | Yes | No |
| Google Gemini | LiteLLM | Yes | Yes (limited) |
| Any LiteLLM provider | LiteLLM | Varies | Varies |
The bootstrap wizard offers curated model lists for each provider. See
Model Catalog for the full list, or run python -m yoker
with no config to launch the wizard.
Security: API keys are collected via masked input during bootstrap, and
config files are written with chmod 600 permissions. API keys can also be
injected from environment variables using Clevis interpolation
(${OPENAI_API_KEY}). Keyring integration for OS keychain storage is planned
(TODO S.1).
Documentation
- Full documentation
- Installation guide
- Quick start
- Model catalog - Curated models per provider
- Why Yoker? - Project rationale and comparison
- Architecture
Development
git clone https://github.com/christophevg/yoker.git
cd yoker
make env-dev # Create virtual environment and install dependencies
make test # Run tests with coverage
make check # Type checking + linting
make docs # Build documentation
Requires Python 3.10+. Uses uv for dependency management. See CLAUDE.md for project conventions.
Contributing
Contributions welcome! Please read CLAUDE.md for project conventions and development guidelines.
Changelog
See GitHub Releases for version history.
License
MIT License - see LICENSE for details.
Name: "yoker" - One who yokes. The agent noun from "yoke" (PIE yeug- meaning "to join"). Pairs with "clitic" (both are joining tools). See docs/NAME.md for full etymology.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yoker-0.7.0.tar.gz.
File metadata
- Download URL: yoker-0.7.0.tar.gz
- Upload date:
- Size: 8.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94409e7e6a9eade84a64529c9075da443c1c855f9eeadd8899736373b020960e
|
|
| MD5 |
5d61a0e2aadad4373f6ff056025e58f0
|
|
| BLAKE2b-256 |
82481ed51f578f665a9a79aacd24a8fc7e8fb3c1c7833d188d44482a6313f367
|
File details
Details for the file yoker-0.7.0-py3-none-any.whl.
File metadata
- Download URL: yoker-0.7.0-py3-none-any.whl
- Upload date:
- Size: 204.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4dd6bdb681ffcbee0b8521f792de8ca053db1e8cc42c9c6de37b3c73cc43afb4
|
|
| MD5 |
3a618507a56ac9e8a492107f9d086898
|
|
| BLAKE2b-256 |
c1b13b48fcae163ec0a3f94ec21f3210425fbd5a74f3e310416e38063fde3309
|