Metadata-Version: 2.4
Name: sadhan
Version: 0.1.0a1
Summary: A minimal AI agent harness: local models run bash in a real execution loop, with a terminal UI
Keywords: ai,agent,llm,ollama,cli,tui,bash
Author: Gauresh Tambe
Author-email: Gauresh Tambe <gaureshtambe25@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
Requires-Dist: litellm
Requires-Dist: ollama>=0.6.2
Requires-Dist: textual>=8.2.8
Requires-Python: >=3.12
Project-URL: Homepage, https://blog.gauresh.art/demystifying-ai-harnesses
Project-URL: Source, https://github.com/GaureshArt/sadhan
Description-Content-Type: text/markdown

# sadhan

A minimal AI harness built from scratch, functional (not class-based), designed to run local models via Ollama through a real bash-execution loop.

Built as a learning project documenting an [ongoing blog series](https://blog.gauresh.art/series/ai-harness-engineering) on what actually makes an AI agent harness work, not just a bigger model.

## What this is

`sadhan` gives a local LLM (currently tested with `qwen3.5:4b` via Ollama) a real, working agent loop: it can reason about a task, execute a single bash command per turn, see the result, and keep going until it completes the task or hits a hard limit.

It's intentionally minimal. Inspired by [mini-swe-agent](https://github.com/SWE-agent/mini-swe-agent), but built independently with a different architecture, functional instead of class-based, driving the model through native tool calling with a robust fallback for models that emit tool calls as inline JSON.

## Status

Early, active development. Not production-ready. Currently missing:
- No task-completion verification (the model's own "done" claim is trusted as-is)
- No persistent environment (venv/cwd doesn't survive across separate commands yet)
- No vision/screenshot support yet
- No command safety filtering (e.g. nothing currently blocks a destructive command)

## How it works
```
main.py → CLI loop, takes tasks one at a time
└── agent.py → the agent loop: call → tool → observe → repeat
├── llm_call.py → streams a completion with the tool schema, returns a Turn
│                (content + tool_calls), handling both native and inline JSON calls
├── llms/ → provider-agnostic model layer
│   ├── registry.py → discovers available models (Ollama + any LiteLLM provider with credentials)
│   └── client.py → async LiteLLM connector (streams every provider uniformly)
├── tools.py → the run_bash tool, defined as a Pydantic model and exposed
│              as an OpenAI-style function schema
├── bash.py → executes each tool command as a real subprocess (persistent shell)
└── state.py → tracks messages, step count, error count
config.py → model name, step limit, error limit, working directory
prompt.py → system prompt defining the response format and rules
```
Each loop iteration:
1. The model is sent the conversation (system, user, previous assistant/tool pairs) plus the `run_bash` tool schema and responds either by requesting a tool call or, when done, a plain final message.
2. Each requested call carries exactly one bash command, which is executed in the persistent shell.
3. The command's returncode + output are fed back as a `tool` message so the conversation stays valid and the model can recover from failures.
4. A short reasoning line (`✦ {reason}`) is shown before each command in the TUI, extracted from the tool call's `reason` field or the model's own reasoning stream.
5. This repeats until the model responds with no tool call (a final message) or a step/error limit is hit.

## Setup

```bash
pip install sadhan          # once it's published to PyPI
# or, from a checkout:
git clone https://github.com/GaureshArt/sadhan.git
cd sadhan && pip install -e .
```

The `sadhan` and `sadhan-tui` commands are installed as console scripts.

Run sadhan in the project you want it to work on. The working directory is taken
from the current directory by default, or from an explicit path argument:

```bash
sadhan              # work in the current directory
sadhan /path/to/project
sadhan-tui          # same choices, for the terminal UI
sadhan-tui ~/projects/app
```

Edit `config.py` for the other settings:

```python
model = 'qwen3.5:4b'
step_limit = 30
max_errors = 4
timeout = 60
```

`model` is a LiteLLM id. Bare names are treated as Ollama models (`qwen3.5:4b`
→ `ollama/qwen3.5:4b`); any LiteLLM-compatible provider works too
(`openai/gpt-4o-mini`, `anthropic/claude-sonnet-4-20250514`, ...). At startup
sadhan builds its model list from two sources:

- every pulled Ollama model (drop-in compatible, no extra setup)
- the built-in LiteLLM catalog (OpenAI, Anthropic, Gemini, Groq, DeepSeek,
  Mistral, xAI, Together, Fireworks, OpenRouter, Azure, Bedrock, Vertex AI…),
  shown with a `(need key)` hint until the matching API key is set
- `SADHAN_MODEL` to force an active model at runtime, or `SADHAN_MODELS`
  (comma-separated) to add extra model ids

In the TUI (`sadhan-tui`) click the model in the header to pick the active
model from the dropdown; press `ctrl+o` to enter provider API keys directly.
Keys are stored in `~/.sadhan/keys.json` and picked up on restart. `main.py`
uses the registry default.

Make sure Ollama is running and the model is pulled:

```bash
ollama pull qwen3.5:4b
```

## Usage

```bash
python main.py
```

You'll be prompted for a task. It runs until completion or a limit is hit, then prompts for the next task.

## Roadmap

- [ ] Verify task completion against real evidence, not just the model's claim
- [ ] Persistent working state across related tasks (a notes file, not full context replay)
- [ ] Command safety filtering (block destructive commands like `git push`, `rm -rf`)
- [ ] Manual screenshot-as-context support for visual tasks
- [ ] Playwright MCP integration for automated visual verification

## Why "sadhan"

Sadhan (साधन) comes from the Sanskrit root साध् ("to accomplish") — it means the means by which something gets done. An instrument, an agent, even a weapon in older usage, whatever tool actually gets you to the goal.

That's the idea here. A small local model often can't finish a real task alone. Wrapped in sadhan, it has a means to get there, and a way to actually verify it did.

## License
MIT License