Metadata-Version: 2.5
Name: dhrona
Version: 0.1.14
Summary: train agents to choose and use the right tools
Project-URL: Repository, https://github.com/Karthik777/drona
Project-URL: Documentation, https://Karthik777.github.io/drona/
Author-email: Karthik <karthik.rajgopal@hotmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: nbdev
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.12
Requires-Dist: aidialog>=0.0.22
Requires-Dist: fastcore>=2.1.16
Requires-Dist: llmsurgery>=0.0.13
Requires-Dist: rahasya>=0.0.5
Requires-Dist: shalya>=0.1.14
Requires-Dist: uraiyadal>=0.0.1
Description-Content-Type: text/markdown

# dhrona


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

Dhrona studies how a coding agent uses its tools and helps it use them better. It was built for Ramabana, a coding agent that writes every finished turn to a history file: the request, each tool call with its arguments and result, and the reply. Dhrona reads that file.

The sequence of tool calls an agent makes to answer one request is its route. Some routes are poor. The agent runs `git diff` through the shell when it has a `git_diff` tool, sends arguments that do not fit a tool’s signature, or repeats a call that has just failed. Dhrona finds these mistakes and scores each route. It can then teach the agent in two ways. You can keep good sessions as examples, called rounds, and put them in front of a new chat so the model sees good tool use before the first request. Or Dhrona can replay past requests and edit the agent’s learned folder, the notes and skills Ramabana loads into every session, keeping an edit only when the replay goes better.

A few other names appear on these pages. Leela is the notebook editor in which you review a round. Aidialog is the notebook format a round is saved in. Urai is the chat library whose message format a warm start is written in, and Rishi re-exports the same format.

## How the pieces fit

1.  Measure. `dhrona-tools` counts calls, failures and shell bypasses for each tool and each model. `dhrona` scores the routes in a history.
2.  Capture. `dhrona-capture` saves a finished Ramabana session as a review notebook.
3.  Review. Open the notebook in Leela, delete the detours and anything private, and keep the route you want future models to copy.
4.  Accept. `dhrona-accept --install` records who reviewed the round, which tools it calls and which model ran it, writes the round as JSON and copies it into the rounds library.
5.  Warm start. `warm_start(tools, model)` turns accepted rounds into the opening history of a new chat. It leaves out any round whose calls no longer fit the tools you offer.

Run `dhrona-tools` again after a change to see whether tool use moved.

## Install

``` sh
pip install dhrona
```

## Measure tool use

`dhrona-tools` reads the Ramabana and Leela history files and reports, for each tool, how often it was called, how often it failed and how often a person refused it. It also counts the shell commands that did the job of a dedicated tool, and repeats the counts for each model. Save its JSON before you change a tool set and compare it with a later run.

``` sh
dhrona-tools --since 2026-09-19
dhrona-tools --json > baselines/tools.json
```

[`assess_turn`](https://Karthik777.github.io/drona/core.html#assess_turn) scores one turn by its tool calls alone. It does not read the assistant’s text to judge the quality of the work. This turn was asked to use FOSSICK on a GitHub repository but began with a web search:

``` python
turn = {
    'prompt': 'Use fossick to research https://github.com/AnswerDotAI/llmdojo',
    'activity': [{'tool': 'web_search', 'ok': True, 'args': {'query': 'llmdojo'}}],
}
assessment = assess_turn(turn)
assessment
```

Each problem is a [`Finding`](https://Karthik777.github.io/drona/core.html#finding), and its `kind` says what went wrong:

| kind | what happened |
|----|----|
| `route` | the request named GitHub and FOSSICK, but the first research call was not `fossick read-gh-repo` |
| `schema` | the tool could not decode the arguments the model sent |
| `bypass` | a shell command did the job of a dedicated tool, such as `git diff` for `git_diff` |
| `tool_protocol` | a shell command failed with a usage error |
| `repeat_failure` | the model repeated, with the same arguments, a call that had already failed |
| `denial_retry` | the model repeated a call the person had refused, instead of asking them |

The score starts at 100 and loses 20 points per finding, down to 0. Each dedicated tool a turn bypasses counts once, so shelling out for both `git diff` and `cat` costs 40 points. Run `dhrona` to assess the default Ramabana history, and pass `--session` to assess one session.

Some tool calls need a person’s approval. When the person refuses, or nobody answers in time, Ramabana records a failed row of kind `ask` whose detail starts with `Denied by human operator` and gives the reason. Dhrona does not treat a refusal as a tool failure, so it earns no `schema` or `repeat_failure` finding. `dhrona-tools` counts refusals separately as `denials`, with the most common `denial_reasons`.

The mistake Dhrona does flag is making the refused call again without asking the person, either later in the same turn or in the next turn of the session when the reply in between asked no question. That is a `denial_retry`. The packaged `approval-refused` seed shows the better answer: name the refused edit, say that nothing changed, and ask one question.

## Curate a round

A round is one short session kept as an example of the right way to use the tools. You make one from a real session. Ramabana already records each finished turn, so capture reads that record after the conversation ends. It does not watch a running agent.

``` sh
ramabana --root /path/to/project
# finish the useful conversation, then quit
dhrona-capture training/github-research.ipynb --session latest
leela training
```

Open `github-research.ipynb` in Leela. Delete the detours and anything private, and keep the route future models should copy. Save the notebook, then accept it under your name:

``` sh
dhrona-accept training/github-research.ipynb --reviewer Karthik --install
```

Accepting writes the reviewer, the tools the round calls and the model that ran it into the notebook’s metadata, and writes `github-research.json` beside the notebook with the round’s messages. `--install` also copies that file into the rounds library, `~/.config/dhrona/rounds/`, where [`warm_start`](https://Karthik777.github.io/drona/core.html#warm_start) looks for rounds. A notebook that has not been accepted cannot start a session.

`dhrona-start` prints the two Ramabana commands that start a session from an accepted round: one sends the round as the first turn, and the other resumes that session. `--launch` runs them.

``` sh
dhrona-start training/github-research.ipynb --root /path/to/project
dhrona-start training/github-research.ipynb --root /path/to/project --launch
```

Ramabana has no option for loading prepared history, so the first command sends the whole round as one prompt that ends by asking for the reply `DHRONA_READY`. Ramabana saves that turn, and the second command resumes the session for real work. Leela can read the same history directly with [`compiled_history`](https://Karthik777.github.io/drona/rounds.html#compiled_history) once it supports prepared history.

## Warm start a chat

A warm start puts a few accepted rounds at the front of a new chat’s history, so the model sees good tool use before the first real request. [`warm_start`](https://Karthik777.github.io/drona/core.html#warm_start) builds that history in Urai’s message format from two places: your rounds library and the seeds packaged with dhrona.

Pass the tools you will offer, as a list of callables or names, or as a dict from name to callable. A round is used only if each of its recorded calls still binds to the offered tool’s signature ([`call_valid`](https://Karthik777.github.io/drona/core.html#call_valid)), so a round for a tool that has since been renamed or changed is left out rather than taught. Rounds are ordered by `meta.rank`, lower first, then by whether they were recorded on the same model, and `limit` (2 by default) keeps the first few. Pass the result to any Urai or Rishi chat as `messages=`, or call [`prepare_chat`](https://Karthik777.github.io/drona/core.html#prepare_chat) on an empty chat.

Seven seeds ship with dhrona. Each is one short session on the shalya 0.1.0 and ramabana 0.2.0 tool set:

| seed | rank | calls | lesson |
|----|----|----|----|
| `git-flow` | 10 | `git_status`, `git_diff`, `git_commit(message, paths)`, `git_divergence`, `git_remote(op='push')` | use the git tools for git, and note the `undo` token a commit returns |
| `search-choice` | 20 | `search_code`, `grep(regex=False)`, `ls` | semantic search for a concept, literal search for callers, a listing for a folder |
| `notebook-edit` | 30 | `notebook_cells`, `view_cell`, `edit_cell(path, cell_id, edits)` | edit a notebook by cell id |
| `approval-refused` | 40 | `view_file`, then a refused `replace_text(path, edits)` | name the refused edit, say nothing changed, ask once |
| `memory-key` | 50 | `remember(key=)` twice, then `memory_search` | a note saved under an existing key replaces the old one |
| `fossick-github` | 50 | `run_shell` running `fossick read-gh-repo` | read a GitHub repository with FOSSICK first |
| `delegation` | 60 | `delegate_search(questions=[...])`, then `view_file` | check a sub-agent’s report against the lines it cites |

With only `run_shell` offered, the `fossick-github` seed is the only one that fits:

``` python
def run_shell(command, cwd='.', timeout=60): ...
history = warm_start([run_shell])
[(message['role'], message.get('name')) for message in history]
```

[`register_completion`](https://Karthik777.github.io/drona/core.html#register_completion) writes a completion receipt that records the package version and the round revision. [`completion_valid`](https://Karthik777.github.io/drona/core.html#completion_valid) rejects a receipt written for an older round.

## Improve the learned folder

Ramabana loads a learned folder into every session: an `AGENTS.md` of short rules, and skills it opens when a task needs them. [`improve_round`](https://Karthik777.github.io/drona/improve.html#improve_round) tries to improve that folder from one finished session, following the propose, probe and compose step of Harness². It replays the session’s request from the commit the session started at, `k` times under the current folder. An editor model reads those runs and edits a copy of the folder, and the request is replayed once more under the copy. The copy is kept only when that run replied, took a cheaper route than every earlier run, and a judge model rated its reply at least as good as the best of theirs. A route’s cost is its findings, then its failed calls, then its number of calls, and refused approvals do not count. A kept copy waits on a `pending/` branch of the learned folder’s own git repository. Ramabana reads only what a person accepted, with `ramabana-learned accept`. The editor also reads the lessons the agent recorded with `remember`, and a lesson about a broken tool can become a skill that works around it. Emails, phone numbers, keys and other personal details are scrubbed with rahasya before the editor or the judge sees anything, and before anything is kept. A skill or rule Dhrona proposed that goes 30 days without use expires at the next batch. When accepted lessons crowd `AGENTS.md`, [`compact_learned`](https://Karthik777.github.io/drona/improve.html#compact_learned) asks the editor to merge and shorten the paragraphs Dhrona wrote, leaves every paragraph a person wrote as it was, and proposes the result on a `pending/` branch for review like any lesson. A night does the same once `AGENTS.md` passes 80% of its 2,000 characters.

Ramabana writes the commit a session started at to `<cfg>/runs/<session>/start.json`. A session that began with uncommitted changes is refused, because a replay from that commit would not see them. A session resumed without that file writes none, and Dhrona then infers its start from the history.

``` python
from dhrona.improve import improve_round
start = json.loads(Path('~/.config/ramabana/runs/agent_.../start.json').expanduser().read_text())[0]
report = improve_round(prompt, start, '/tmp/round-1', learned=Path('~/.config/ramabana/learned').expanduser(),
                       model='claude/haiku', editor='claude/sonnet')
```

Every run, the evidence the editor read, the candidate folder and `report.json` stay in the work folder, so you can read the candidate’s reply and the judge’s verdict before relying on a kept round.

`dhrona-improve` runs rounds on past sessions one after another. It picks first turns that make sense on their own and whose route cost something, finds the repository their tool paths touched, and replays each from that repository’s last commit before the turn. Each round starts from the folder the previous round left. `summary.json` in the batch folder holds one row per round, and a round that fails becomes a row with its error. [`nightly`](https://Karthik777.github.io/drona/improve.html#nightly) runs the same kind of batch from a scheduled beat, within a budget in dollars.

``` sh
dhrona-improve /tmp/batch-1 /tmp/learned --n 10 --editor claude/sonnet --models claude-opus-5-5,claude/sonnet
```

## Move sessions between hosts

A host is a program that runs agent sessions: Ramabana, Claude Code or Codex. A session from any of the three can be imported as an Aidialog notebook and reviewed in Leela.

``` sh
dhrona-import ramabana training/round.ipynb --session latest
dhrona-import claude training/round.ipynb --session latest --cwd /path/to/project
dhrona-import codex training/round.ipynb --session latest --cwd /path/to/project
```

The same reviewed notebook can then be exported as a Ramabana bootstrap prompt, a Claude Code session you can resume, or Codex Responses API items.

``` sh
dhrona-export training/round.ipynb ramabana --output training/round.txt
dhrona-export training/round.ipynb claude --cwd /path/to/project
dhrona-export training/round.ipynb codex --output training/round-items.json
```

Claude Code can resume the session id that the Claude export prints. The Codex export cannot make a resumable Codex session, because llmsurgery, the library Dhrona uses to read and write host sessions, has no public writer for Codex rollout files yet. The exported items still serve for inspection and for datasets.

## Command line

| Command | Purpose |
|----|----|
| `dhrona` | Score tool routes in Ramabana history |
| `dhrona-tools` | Tool use, failures and bypasses per tool and per model |
| `dhrona-capture` | Save a Ramabana session as a review notebook |
| `dhrona-accept` | Accept a reviewed round; `--install` adds it to the library |
| `dhrona-start` | Bootstrap and resume Ramabana with an accepted round |
| `dhrona-import` | Import a Ramabana, Claude or Codex session as a notebook |
| `dhrona-export` | Export a notebook for Ramabana, Claude or Codex |
| `dhrona-improve` | Run improvement rounds on past Ramabana sessions |

## Develop

``` sh
uv sync --all-extras --group dev
uv run nbdev-prepare
```
