Metadata-Version: 2.5
Name: shalya
Version: 0.1.14
Summary: the tools an agent is given, and the host that answers them
Project-URL: Repository, https://github.com/vedicreader/shalya
Project-URL: Documentation, https://vedicreader.github.io/shalya/
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
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.12
Requires-Dist: dhrishti
Requires-Dist: exhash>=0.4.9
Requires-Dist: fastcore>=2.1.16
Requires-Dist: fastspec
Requires-Dist: fossick>=0.1.17
Requires-Dist: gheasy>=0.0.10
Requires-Dist: jupyter-client
Requires-Dist: litesearch>=0.1.34
Requires-Dist: vishalakshi>=0.1.17
Provides-Extra: dev
Requires-Dist: fastmux>=0.0.2; extra == 'dev'
Requires-Dist: nbdev>=3.3.11; extra == 'dev'
Requires-Dist: notebook>=7.6.1; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Provides-Extra: tmux
Requires-Dist: fastmux>=0.0.2; extra == 'tmux'
Description-Content-Type: text/markdown

# shalya


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

An LLM agent works by calling tools: read this file, search for that function, run this command. shalya (शल्य) provides those tools as plain Python functions, each with the type hints and docstring a model reads, each returning the text the model sees next. It does not run an agent loop or talk to a model. A harness such as [ramabana](https://github.com/vedicreader/ramabana) does that, and asks shalya for the tools.

Two ideas carry the package. A host is the application the agent works inside: the folders it may touch, and whatever else it offers, such as a shell, a Python session, web access or a memory store. A host declares what it supports as capability groups, such as `code`, `web` or `shell`. `tools_for(host)` builds the tools of every group the host supports, bound to that host, and returns them as one list. A host that cannot serve a group simply gets no tools for it, so a model is never offered a tool that cannot work.

## Install

``` sh
pip install shalya
```

``` python
from shalya.core import failed
from shalya.host import LocalHost
from shalya.tools import tools_for, read_only
from shalya.skills import Skill, Registry, skill_index
```

## A host

[`LocalHost`](https://vedicreader.github.io/shalya/host.html#localhost) is the host for folders on your own machine. It reads and writes only inside the folders you open, called roots. `can(group)` asks whether it supports a group, and `without` lists the groups this instance has to go without. Here memory and API specifications are missing because no backend was given, and web access was switched off.

``` python
import shutil
from pathlib import Path
```

``` python
d = Path('/tmp/shalya-demo')
shutil.rmtree(d, ignore_errors=True); d.mkdir(parents=True)
(d/'greet.py').write_text('def hi(n): return f"hi {n}"\n')

host = LocalHost(roots=[d], index=False, web=False)
host.can('file'), host.can('memory'), sorted(host.without)
```

    (True, False, ['api', 'ask', 'memory', 'watch', 'web'])

## The tools

[`tools_for`](https://vedicreader.github.io/shalya/tools.html#tools_for) returns one flat list, built from the groups this host supports. Every tool takes strings and simple values and returns a string, so a harness can pass the list to a model without knowing anything about hosts.

``` python
ts = {t.__name__: t for t in tools_for(host)}
len(ts), sorted(ts)
```

    (28,
     ['add_cell',
      'add_root',
      'create_file',
      'edit_cell',
      'git_checkout',
      'git_commit',
      'git_diff',
      'git_divergence',
      'git_log',
      'git_remote',
      'git_stash',
      'git_status',
      'grep',
      'inspect_python',
      'ls',
      'notebook_cells',
      'outline',
      'read_terminal',
      'replace_text',
      'restart_kernel',
      'run_python',
      'run_shell',
      'run_shell_bg',
      'search_code',
      'shell_output',
      'shell_stop',
      'view_cell',
      'view_file'])

A tool answers with the text a model reads. `view_file` shows a file with a line number and a short hash of each line. `replace_text` edits by exact text: it takes a list of `{oldText, newText}` pairs, each `oldText` must match exactly once, and either all of them apply or none does. `edit_file`, an editor that addresses lines by their hashes and refuses a stale address, is the opt-in `exhash` group: `tools_for(host, optin=('exhash',))`.

``` python
print(ts['view_file']('greet.py'))
```

    1|f8c6|def hi(n): return f"hi {n}"

``` python
print(ts['replace_text']('greet.py', [{'oldText': 'hi {n}', 'newText': 'hey {n}'}]))
```

    replaced 1 block(s) in /private/tmp/shalya-demo/greet.py
    --- a//private/tmp/shalya-demo/greet.py
    +++ b//private/tmp/shalya-demo/greet.py
    @@ -1 +1 @@
    -def hi(n): return f"hi {n}"
    +def hi(n): return f"hey {n}"

Tools never raise, because an exception would end the agent’s turn. A tool that cannot do what was asked returns a string beginning with `ERROR:`, and [`failed`](https://vedicreader.github.io/shalya/core.html#failed) is the function that checks for it.

``` python
r = ts['view_file']('missing.py')
failed(r), r
```

    (True, 'ERROR: no such file: /private/tmp/shalya-demo/missing.py')

## A browser

[`LocalHost`](https://vedicreader.github.io/shalya/host.html#localhost) has no browser. A host that inherits [`BrowserHost`](https://vedicreader.github.io/shalya/host.html#browserhost) can offer the opt-in `browser` group (`optin=('browser',)`). `browse`, `screenshot`, `page_text` and `page_reload` look at a page without changing anything. `page_click`, `page_type` and `page_eval` act on the page, so they count as writes and go to a person for approval. `screenshot` returns the path of the PNG it saved, because a tool result is text.

## Opting in

`optin` names the groups nobody gets by default: `exhash` (`edit_file`), `research` (a cited digest of the top web results), `author` (`create_skill`), `browser` (see above) and `legacy`. The legacy group is one release of shims for old tool names an MCP client may still send: `list_files`, `list_vars`, `environment`, `memory_tree`, `git_rebase_preview`, `set_reminder` and `watch_url`. They are removed in 0.2.0. An opt-in group the host cannot back is empty, not broken.

``` python
sorted({t.__name__ for t in tools_for(host, optin=('exhash', 'legacy'))} - set(ts))
```

    ['edit_file', 'environment', 'git_rebase_preview', 'list_files', 'list_vars']

A host with a memory backend, a [vishalakshi](https://github.com/vedicreader/vishalakshi) vault (0.1.17 or later), gets the memory and watch tools too. `memory_read(ref='')` browses memory: with no argument it lists every document, with a document it shows its headings, and with a section id such as `doc#3` it returns that section. `remember(key=...)` replaces the note filed earlier under the same key. One `watch(target, kind=...)` tool re-checks a `url`, repeats a web `search`, watches a `folder`, or files a `remind`er on a schedule.

The git tools that change a repository answer in JSON: gheasy’s [`summary`](https://vedicreader.github.io/shalya/core.html#summary), the new `head`, whether the branch `moved`, and an `undo` token a harness can use to rewind what a turn did.

## Tools for a sub-agent

A sub-agent sent to look something up should not change anything. [`read_only`](https://vedicreader.github.io/shalya/tools.html#read_only) filters a tool list down to what it may have, dropping every write tool unless the tool offers a read-only version of itself. `read_url` offers one, which reads a page without saving it to memory. The cell below lists the tools [`read_only`](https://vedicreader.github.io/shalya/tools.html#read_only) removed.

``` python
sorted(set(ts) - {t.__name__ for t in read_only(ts.values())})
```

    ['add_cell',
     'add_root',
     'create_file',
     'edit_cell',
     'git_checkout',
     'git_commit',
     'git_remote',
     'git_stash',
     'replace_text',
     'restart_kernel',
     'run_python',
     'run_shell',
     'run_shell_bg']

## Skills

A skill is a document of instructions for one kind of task. [`skill_index`](https://vedicreader.github.io/shalya/skills.html#skill_index) renders the block that goes in the system prompt: each skill’s name and a clipped description, never its body, which the agent reads with `read_skill` when it needs it. [`discover`](https://vedicreader.github.io/shalya/skills.html#discover) collects the skills installed packages publish as pyskills and every `SKILL.md` under the open folders.

``` python
ks = [Skill(name='exhash', source='md', description='Hash-verified text editing.', where='', _text='View, then edit by address.'),
      Skill(name='ghapi', source='md', description='GitHub REST access through `GhApi`.', where='', _text='Issues, pull requests, releases.')]
print(skill_index(ks))
```



    ## Skills

    Use `read_skill(name)` before doing the work a skill covers. Installed tool code is also searchable with `search_code`.

    - `exhash`: Hash-verified text editing.
    - `ghapi`: GitHub REST access through `GhApi`.

## Extensions

An extension is a Python file with a `setup(reg)` function. Through `reg`, a [`Registry`](https://vedicreader.github.io/shalya/skills.html#registry), it can add tools, skills, slash commands, lifecycle hooks and an approval policy. Hooking an event name that does not exist raises an error, so a typo cannot silently hook nothing.

``` python
reg = Registry()

@reg.tool
def weather(city: str) -> str:
    "Today's weather in `city`."
    return f'sunny in {city}'

reg.skill('house-style', 'Short sentences. No headings.')
[t.__name__ for t in reg.tools], [s.name for s in reg.skills]
```

    (['weather'], ['house-style'])

## Develop

``` sh
uv sync --all-extras --group dev
uv run nbdev-export
uv run nbdev-test
uv run pytest
uv run nbdev-clean
```
