Skip to main content

ramabana

ramabana is the brain of a coding agent: policy, tools, memory, and routing. It has no editor and no frontend. The application it runs inside speaks one protocol, Host; models arrive through rishi.

It grew up inside leela and never imported the IDE, so it now stands alone with leela as its first consumer. It ships two frontends of its own – a terminal app and an MCP server – on that same Host.

What you get

Capability Built on What it does
Any model runtime rishi LiteRT, MLX, llama.cpp, Cursor and hosted APIs behind one Chat
Code index kosha FTS + vectors + call graph over the open folders and installed packages
Search fusion litesearch one ranking from kosha, ripgrep and vault prose (RRF)
Web research fossick search, escalating fetch, cited digests, shopping carts
Durable memory vishalakshi a vault of what was read, federated search, watches
Editing exhash hash-verified line addressing, so a stale read fails instead of damaging a file

Two decisions shape everything else. Routing keeps the cheap jobs – labels, summaries, inline completion, the compaction checkpoint – on a small local model while the turn runs wherever you chose. Capability probing means a tool the host cannot perform is never offered to the model, so a partly built host gives a smaller agent rather than a broken one.

Modules

One notebook per module – the page you read is the module you import.

notebook module owns
00_core ramabana.core errors, environment, and which model runs which job
01_runtime ramabana.runtime the rishi adapter, usage, native diagnostics, compaction
02_tools ramabana.tools Host, LocalHost, the tools, skills, extensions, sub-agents
03_agent ramabana.agent approvals, the activity feed, Agent, inline completion
04_testing ramabana.testing a full-capability host and the backend doubles
05_cli ramabana.cli the terminal app, on teleprint
06_mcp ramabana.mcp the same tools, served over MCP
07_vault ramabana.vault durable memory, federated search and watches
08_shop ramabana.shop a trolley an agent can fill, on fossick
09_coding_patterns ramabana.coding_patterns the coding standards, published as a skill
10_spec ramabana.spec OpenAPI, Azure and Google Discovery specs, called as tools
11_pyrepl ramabana.pyrepl the kernel and overlay behind --python, on dhrishti

Install

pip install ramabana                 # the harness
pip install 'ramabana[cli]'          # ...and the terminal app
pip install 'ramabana[pyrepl]'       # ...and the Python prompt inside it
pip install 'ramabana[mcp]'          # ...and the MCP server
pip install 'ramabana[all]'

A model is not bundled. rishi fetches one on first use, and which one is a routing decision – see core. LiteRT models run on the GPU and fall back to the CPU with a warning where there is no delegate; RAMABANA_LITERT_BACKEND=cpu pins them to the CPU.

Use

An agent needs a host, and LocalHost is one over real folders. Nothing was downloaded here, so the agent is not ready – and it says so instead of raising.

from ramabana import Agent
from ramabana.tools import LocalHost, tools_for

host = LocalHost(['..'], web=True)
agent = Agent(host, extensions=False)
len(agent.tools), agent.ready, agent.note
(23, False, 'not started')

LocalHost starts Kosha.sync automatically, in a daemon thread, over every open root. Indexing overlaps model startup instead of delaying the first prompt, and search falls back to a literal scan until the semantic + keyword index is ready.

host.wait_index(120), host.search_note
(True, 'Kosha semantic + keyword index over 1 folder(s) and environment')
[(h.path, h.line, h.symbol) for h in host.search('drop the thinking from a streamed reply')[:3]]
[('/Users/71293/code/personal/orgs/ramabana/ramabana/runtime.py',
  933,
  'ramabana.runtime.RishiBackend._stream'),
 ('/Users/71293/code/personal/orgs/ramabana/ramabana/runtime.py',
  717,
  'ramabana.runtime.ThinkFilter'),
 ('/Users/71293/code/personal/orgs/ramabana/ramabana/cli.py',
  273,
  'ramabana.cli.Ui.stream')]

Anything the host cannot do is not offered to the model at all:

from ramabana.tools import NullHost
len(tools_for(NullHost())), len(tools_for(host))
(10, 19)

The sandbox has two halves. Confining writes stops an agent damaging something nobody opened; confining reads stops it answering from a sibling checkout, a library outside the venv, or a log the project does not own – which is a cost as often as a protection. read_outside=True separates them: a read may name any path on the machine, a write may not, and credential paths are refused either way. Enumeration stays confined regardless, so walk, grep and list_files never leave the open folders and a read outside is always a path the model already knew.

open_host = LocalHost(['..'], web=False, index=False, read_outside=True)
open_host.roots_note

One turn is ask. Here it runs against a scripted backend so the page is reproducible; testing is where that comes from.

from ramabana.testing import fake_agent

scripted, backend = fake_agent(replies=['`threshold` is in `ramabana/runtime.py`.'])
scripted.ask('where is the compaction threshold?')
'`threshold` is in `ramabana/runtime.py`.'

The turn is inspectable afterwards – what it called, what it changed, what it cost:

scripted.calls, scripted.changes(), repr(scripted.use)
([('search_code', {'query': 'where is the compaction threshold?'})],
 {},
 '15 tok · in 10 · out 5 · model')

In a terminal

ramabana --root . --model gpt-mini      # a transcript of blocks, a status bar, one line to type in
ramabana --python --root .              # the same program, with Python as a mode inside it
ramabana-mcp --root .                   # the same tools, served to another agent
ramabana --approve auto --prompt "$TASK"  # no terminal at all: the answer, on stdout

In the terminal app (cli): tool calls are foldable blocks rather than lines, writes stop for approval, a request whose reading matters (“refactor the runtime”) offers a row of options before it becomes a turn, and every slash command is the agent’s own. Drop or paste a path to attach a picture or a sound file, @path names one inline, and /copy (or y in the transcript view) yields the model’s own Markdown rather than the terminal’s wrapping of it.

At the Python prompt (pyrepl): /python takes the line and /agent hands it back. The namespace belongs to whoever is typing – Ramabana reads it and builds in a layer of its own, and cannot rebind a name it did not create. Tab completes from the live kernel with each candidate’s type and shape beside it, and /promote NAME adopts one of the agent’s variables into your namespace.

Over MCP (mcp): read-only by default, writes behind --write, and --model adds an ask tool that runs a whole Ramabana turn and returns just its answer.

--read_outside is the switch described above, and --vault keeps what a session reads in a vishalakshi vault, which adds the five memory_* tools so the next session starts knowing what this one learned. --spec adds api_load, api_ops and api_call (spec).

One hard problem, end to end

Everything above is hermetic. Everything below is not: it runs a real agent over this repository, against the real internet. The cells are eval: false so neither CI nor the docs build tries them – the outputs shown are real, captured on an Apple silicon laptop.

The problem needs both halves of the harness: one fact is in this repo and only a code index finds it, the other is a price on a supermarket site that turns scrapers away. The turn drives a twenty-three tool loop on a hosted model while every cheap job runs on a 9B on the laptop.

from ramabana.agent import Agent
from ramabana.tools import LocalHost

host = LocalHost(['..'], web=True)          # this repository, and the network
host.wait_index(600)                        # let Kosha finish, so search is semantic

agent = Agent(host, model='gpt-mini', extensions=False)
for job in ('inline', 'completion', 'classify', 'summary', 'subagent'):
    agent.routing.set('ornith-9b', job)
agent.start() is not None, agent.note, len(agent.tools)
(True, 'gpt-mini · cloud · 1050k ctx · 23 tools', 23)

Two engines for six jobs, and only one of them is remote:

print(agent.routing.summary())
turn        gpt-mini · cloud · 1050k ctx
inline      ornith-9b · local · 32k ctx
completion  ornith-9b · local · 32k ctx
classify    ornith-9b · local · 32k ctx
summary     ornith-9b · local · 32k ctx
subagent    ornith-9b · local · 32k ctx
sorted(agent.routing.backends())
[('mlx', 'mlx-community/Ornith-1.0-9B-8bit'),
 ('remote', 'openai/gpt-5.6-luna')]

Now the question:

TASK = ("Two facts, then one line of arithmetic.\n"
        "1. In this repository, find the constant the compactor holds back for the model's reply, "
        "and say what it is called and what it is set to.\n"
        "2. Find what Arnott's Tim Tam Original 200g costs at Coles right now, in AUD.\n"
        "Finish with one line: the constant, the price, and how many packs those tokens would "
        "buy at $0.001 per token.")

answer = agent.ask(TASK)
print(answer)
1. The compactor’s reply headroom constant is **`RESERVE`**, set to **16,384 tokens**.
2. Arnott’s Tim Tam Original 200g costs **AUD $6.00** at Coles. ([Coles](https://www.coles.com.au/product/arnott's-tim-tam-chocolate-biscuits-original-200g-329607))

**`RESERVE` (16,384 tokens) × $0.001/token = $16.384 ÷ $6.00 = 2.7307 packs** (2 whole packs).

Both halves are right, and both were found rather than recalled: RESERVE = 16_384 really is the compactor’s output reserve in ramabana/runtime.py, and $6.00 really was the shelf price.

for name, args in agent.calls: print(name, {k: str(v)[:64] for k, v in args.items()})
search_code {'query': 'Two facts, then one line of arithmetic.\n1. In this repositor'}
search_code {'query': 'compactor holds back model reply constant tokens reply budget'}
view_file {'path': '/Users/71293/code/personal/orgs/ramabana/ramabana/runtime.py', 'start': '230', 'end': '285'}
web_search {'query': "site:coles.com.au Arnott's Tim Tam Original 200g price"}
read_url {'url': "https://www.coles.com.au/product/arnott's-tim-tam-chocolate-bis", 'remember': 'False'}

Five calls: search the index, search again with better words, read the one file that matched, search the web, read the one page that mattered. Nothing was written – and changes() is how a frontend knows that without diffing the disk.

repr(agent.use), agent.changes(), agent.problems
('43,830 tok · in 43,179 · out 651 · cached 90% · gpt-5.6-luna', {}, [])

read_url is doing more than it looks. Coles answers a plain fetch with 200 OK and an empty shell, so escalating on the status code never fires; and the price is not in the page’s prose at all, because readability extraction keeps the ingredient list and throws the price away. So the host judges the extracted text, escalates to a real browser when there is too little of it to be a page, and hands the model the page’s schema.org JSON-LD alongside the prose. That block is a standard, not a selector for one shop:

import json
page = host.read_url("https://www.coles.com.au/product/arnott's-tim-tam-chocolate-biscuits-original-200g-329607")
ld = json.loads(page.text.partition('</structured-data>')[0].removeprefix('<structured-data>\n'))
ld[0]['name'], ld[0]['offers'][0]['price'], ld[0]['offers'][0]['priceCurrency']
("Arnott's Tim Tam Chocolate Biscuits Original | 200g", 6, 'AUD')

The cheap jobs ran on the 9B on the laptop. A label costs thirty-two tokens of output, so a reasoning model has to be told not to think: asked to deliberate inside that budget it spends all of it deliberating and there is no answer left to strip the thinking off of. See runtime.

agent.classify('the price came back from coles', ['success', 'failure'])
'success'
agent.summarise(answer)
"The compactor's reply headroom of 16,384 tokens (valued at $16.38) is sufficient to purchase 2 packs of Arnott's Tim Tam Original 200g biscuits at $6.00 each."

The same task without the terminal, for a pipe – the answer on stdout and nothing else:

$ ramabana --model gpt-mini --approve auto --prompt "$TASK"
1. **Constant:** `RESERVE`, set to **16,384 tokens** -- headroom for the model's reply and tool results.
2. **Coles price:** Arnott's Tim Tam Original 200g is **A$6.00**.

**`RESERVE` (16,384 tokens) × A$0.001 = A$16.384 ÷ A$6.00 = 2.7307 packs (2 whole packs).**

Running the turn on the local model is one flag, and the reply is the reply – a template-primed reasoning model’s deliberation never reaches the transcript:

$ ramabana --model ornith-9b --prompt 'Reply with exactly the word: pong'
pong

Develop

The notebooks in nbs/ are the source. Never edit the generated modules.

uv sync --all-extras --group dev
uv run nbdev-export           # notebooks -> ramabana/*.py
uv run nbdev-test             # execute every notebook
uv run pytest                 # the plain-python suite, one file per feature block
uv run nbdev-clean            # before committing

nbdev-test skips nothing by default; the real-model cells above are eval: false and are never executed by it.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ramabana-0.1.4.tar.gz (431.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ramabana-0.1.4-py3-none-any.whl (161.2 kB view details)

Uploaded Python 3

File details

Details for the file ramabana-0.1.4.tar.gz.

File metadata

  • Download URL: ramabana-0.1.4.tar.gz
  • Upload date:
  • Size: 431.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for ramabana-0.1.4.tar.gz
Algorithm Hash digest
SHA256 080701968a2872ac05a6a96c72848dcdcb298f4569eef8a2dca00d963e68f013
MD5 204ae895136c3a8f72a198cf9df065ed
BLAKE2b-256 a896a50cf106968fe2b8b086606d4af22d956445499afbc6e01ee7150aec696b

See more details on using hashes here.

File details

Details for the file ramabana-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: ramabana-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 161.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for ramabana-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 f6b8184c71ba91605fde5cfb3ae1478b6d09d35d90b5895aa13ebbda1930ff6d
MD5 5c33a131c8a158cdb4f31ef8cd654ca8
BLAKE2b-256 a9e8c13c06744c037a6810b9e8df8aba8f6c5c8f326a0a4242174ffbfd0d6419

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page