Skip to main content

dhrona

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

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.

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

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:

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, 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.

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:

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 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.

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 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 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), 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 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:

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

register_completion writes a completion receipt that records the package version and the round revision. 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 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 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.

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 runs the same kind of batch from a scheduled beat, within a budget in dollars.

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.

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.

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

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

Metadata

Release files for dhrona 0.1.14

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dhrona 0.1.14
File Size Uploaded Provenance
dhrona-0.1.14.tar.gz 45.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for dhrona 0.1.14
File Interpreter ABI Platform
dhrona-0.1.14-py3-none-any.whl Python 3 none any Details

Total release size: 96.8 kB

Release files / dhrona-0.1.14.tar.gz

Download URL dhrona-0.1.14.tar.gz
Size 45.5 kB
Tags Source
SHA-256 checksum
How to use checksums
67d0761215a7a28bf600dd8cbd31e51af537b7efc99a7ca45a9ff24d193fe76d
BLAKE2b-256 checksum
How to use checksums
42a8cb3d0358a41a2217fc64defaa8b00f6c3a56dd0093f0d96205c968d55567
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / dhrona-0.1.14-py3-none-any.whl

Download URL dhrona-0.1.14-py3-none-any.whl
Size 51.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0bb6fdf208be8e9d3749f43a03696179733fa3c4a3f53219061e60801011a03e
BLAKE2b-256 checksum
How to use checksums
c676aeb071e5c90813e4bbadb4a50e50d013646d9855770dc7d99d56cee64916
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for dhrona 0.1.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page