Metadata-Version: 2.4
Name: dhrishti
Version: 0.1.1
Summary: a JSON API for live variable inspection & manipulation inside python runtimes
Project-URL: Repository, https://github.com/vedicreader/dhrishti
Project-URL: Documentation, https://vedicreader.github.io/dhrishti/
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.11
Requires-Dist: fastcore>=2.1.4
Requires-Dist: ipython>=9.15.0
Requires-Dist: numpy>=2.4.6
Requires-Dist: pandas>=3.0.3
Requires-Dist: python-fasthtml>=0.14.9
Requires-Dist: safepyrun>=0.2.5
Description-Content-Type: text/markdown

# dhrishti


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

dhrishti is a JSON API for a live Python namespace. You start it inside the process you care
about — a notebook kernel, a training script, a running web app — and from then on any client can
ask that process what its variables are, expand them one level at a time, page a DataFrame as a
grid, run code in it, and read back what ran. It is the engine behind a variable inspector, not
the inspector itself: there is no UI in this package, and the shape of the frontend is yours.

The same window opens onto AI agents. An agent works your live namespace through a sandboxed
overlay: it reads your variables and creates its own, you watch what it builds over the same API,
and you promote the results worth keeping.

## Install

``` sh
pip install dhrishti
```

Or straight from source:

``` sh
pip install git+https://github.com/vedicreader/dhrishti.git
```

## What it shows

Everything starts from a namespace, a dict of names to values. [`snapshot`](https://vedicreader.github.io/dhrishti/core.html#snapshot) turns one into inspector rows: a name, a type, a short value, and a shape.

``` python
import pandas as pd
from dhrishti.core import snapshot, expand, grid_page
ns = dict(x=42, names=['ada','turing','hopper'], df=pd.DataFrame({'a':[1,2,3], 'b':[4,5,6]}))
for v in snapshot(ns): print(f'{v.name} = {v.value}  ({v.type})')
```

    df = DataFrame [3×2]  (DataFrame)
    names = ['ada', 'turing', 'hopper']  (list)
    x = 42  (int)

Containers expand one level at a time, so a million-row frame costs nothing until you open it. A DataFrame expands into its metadata and columns; anything backed by numpy or pandas can also be paged as a grid.

``` python
for v in expand(ns, ('df',)): print(f'{v.name}: {v.value}')
```

    shape: (3, 2)
    size: 6
    dtypes: Series [2] object
    a: Series [3] int64
    b: Series [3] int64

``` python
grid_page(ns, ('df',))['cells']
```

    [['1', '4'], ['2', '5'], ['3', '6']]

## Serving it

Two entry points, depending on what owns the namespace. Inside a live IPython kernel,
[`serve_in_kernel()`](https://vedicreader.github.io/dhrishti/serving.html#serve_in_kernel) starts the API on a background thread and returns its port. For a plain
process, [`serve()`](https://vedicreader.github.io/dhrishti/serving.html#serve) takes the caller’s globals by default and returns the base URL.

The background thread is the point. Shell messages queue behind the running cell, so a
comm-based variable tree would freeze during exactly the long cell you most want to watch —
while an HTTP server on its own thread keeps answering.

``` python
from dhrishti.serving import serve, owner_token, active
import httpx2 as httpx

x, names = 42, ['ada', 'turing', 'hopper']
server, url = serve(name='readme')          # this process's namespace, over HTTP
rows = httpx.get(f'{url}/api/rows').json()
[n['name'] for g in rows['groups'] for n in g['nodes']]
```

<script>
document.body.addEventListener('htmx:configRequest', (event) => {
    if(event.detail.path.includes('://')) return;
    htmx.config.selfRequestsOnly=false;
    event.detail.path = `${location.protocol}//${location.hostname}:8000${event.detail.path}`;
});
</script>

    ['url',
     'server',
     'names',
     'x',
     'v',
     'ns',
     'active',
     'owner_token',
     'serve',
     'grid_page',
     'expand',
     'snapshot',
     'set_logging',
     'open']

### The endpoints

Accessors are JSON-encoded tuples: `["df", "x", 0]` addresses `df['x'][0]`. One scheme covers
namespace bindings, container children, frame columns and array cells.

| endpoint | what it gives you |
|----|----|
| `GET /api/rows?profile=&sort=` | the grouped node tree; `sort` is `name`\|`recent`\|`type`\|`size` |
| `GET /api/expand?accessor=&offset=` | one level of children, paged |
| `GET /api/grid?accessor=&roff=&coff=` | a windowed grid for anything numpy- or pandas-backed |
| `GET /api/result` | the last expression, kept at the `_` accessor so you can expand into it |
| `GET /api/history?n=` | the rolling exec-history notebook’s code cells |
| `GET /api/sessions`, `/api/session?name=` | logged notebooks, and one by stem |
| `GET /api/envs` | this env’s label plus every other live dhrishti process |
| `GET /api/exec?code=&scope=` | run code — `global` mutates, `isolated` uses a scratch copy |
| `GET /api/set?accessor=&expr=` | assign an expression at an accessor |
| `GET /api/promote?accessor=` | adopt an agent’s variable into the owner namespace |

The last three mutate real state and are gated by a per-server owner token, written to
`<reg_dir>/token-<port>` with mode 0600. A consumer on the same machine reads it and sends it as
`X-Dhrishti-Token` (or `?token=`). Agents are never handed it. This is a courtesy lock against
well-behaved local tools, not a cryptographic boundary — the API binds to localhost and anyone
who can read your files can read the token.

[`active()`](https://vedicreader.github.io/dhrishti/serving.html#active) is how a client finds processes to talk to in the first place: every [`serve()`](https://vedicreader.github.io/dhrishti/serving.html#serve) writes a
small JSON entry into the registry directory, and dead entries are pruned on read.

``` python
tok = owner_token()                                     # what a local consumer would read off disk
httpx.get(f'{url}/api/exec', params={'code': 'y = x * 2', 'token': tok}).json()['ok']
print('y is now', y)                                    # the exec landed in this very namespace
[(e['name'], e['port']) for e in active()]
```

    y is now 84

    [('readme', 8000)]

## Handing your namespace to an agent

This is where it pays off. Start a server with the agent surface on, and a tool-calling model can work the same namespace you are watching. Below drives it with [rishi](https://github.com/vedicreader/rishi), whose `Chat` runs an on-device model, so no API keys are involved. The full walkthrough lives on the [agent page](https://vedicreader.github.io/dhrishti/agent.html).

``` python
from dhrishti.serving import agent_session, AGENT_POLICY
from dhrishti.agent import agent_tools, run_coro, AgentSession

df = pd.DataFrame({'x': range(5), 'y': list('abcde')})   # your live data
sess = agent_session()                                   # the shared, sandboxed session
print(AGENT_POLICY[:120], '...')
```

    agent access: persistent session defined by Sandboxed Python session over a shared owner namespace. You can read and acc ...

`agent_tools(sess)` hands the model two functions, `list_vars` and `run_python`, both bound to the session. `conversation_logger(sess)` mirrors every turn into a transcript notebook. Wire them into a `Chat` and ask for some work:

``` python
from fastcore.docments import docstring
sp = f'You are a Python assistant. you have a persistent session defined by {docstring(AgentSession)} Use builtins where possible. import only once. dont run same code again. check globals check if a module before using it. numpy and pandas are always be avilable. Call list_vars before writing code. tools available: {docstring(agent_tools)}'; sp
```

    'You are a Python assistant. you have a persistent session defined by Sandboxed Python session over a shared owner namespace. You can read and access any owner variable freely; your writes land in your own layer and never touch the owner. Mutating or deleting owner variables is blocked. copy to a new name first (e.g. `df2 = df.copy()`, `lst2 = lst[:]`). Check available variables before writing code. Use builtins where possible. import only once. dont run same code again. check globals check if a module before using it. numpy and pandas are always be avilable. Call list_vars before writing code. tools available: Tools for a tool-calling agent over an AgentSession. `list_vars` shows what is available; call it first. `run_python` executes code in the session.'

``` python
from rishi import *
from rishi.litert import gemma4_e4b
from litert_lm import set_min_log_severity, Backend
from dhrishti.agent import conversation_logger
from fastcore.docments import docstring
set_min_log_severity(5)

chat = Chat(model=gemma4_e4b, sp=sp, tools=agent_tools(sess), cbs=[conversation_logger(sess)], backend=Backend.GPU(),
            approve=hitl_policy({'list_vars':'approved', 'run_python':'approved'}))
chat('Scale the numeric columns of df to 0..1 as a new frame df_norm.')
```

The numeric columns of `df` have been successfully scaled to the range of 0..1 and the result is stored in the new frame `df_norm`. The output shows the first few rows of `df_norm` with the normalized values.

``` python
sess.ns['df_norm']
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

|     | x    | y   |
|-----|------|-----|
| 0   | 0.00 | a   |
| 1   | 0.25 | b   |
| 2   | 0.50 | c   |
| 3   | 0.75 | d   |
| 4   | 1.00 | e   |

</div>

An agent driving the API instead of these in-process tools uses `/agent/api/*`: the same row and
grid shapes, but everything flows through the overlay and the restriction list, and the server’s
`agent=` mode decides how much is allowed — `restricted` gives the persistent overlay, `readonly`
forces every cell into an isolated sandbox, `off` refuses the surface entirely.
`/agent/api/sessions` and `/agent/api/transcript` expose the transcripts, so a frontend can offer
a picker over everything the agent has ever done.

## What the agent can’t do

The session is sandboxed. The agent reads anything and binds its own names, but the moment it tries to change something you own, or reach for the filesystem or shell, the cell is refused and the reason comes back in place of a result.

``` python
print(sess.run("df['z'] = 0").error)   # mutating what you own is refused
print(sess.run("import shutil; shutil.rmtree('/nope')").error) # filesystem escapes are refused
list(df.columns)    # your df is exactly as it was
```

    blocked: cannot modify owner variable 'df' in place; bind your result to a new name instead
    blocked: 'shutil.rmtree' is a shell/filesystem escape and is not allowed in the agent session; use your own shell tool

    ['x', 'y']

## Promote what you want to keep

`/agent/api/rows` reports what the agent has built. `/api/promote` moves one of those names into
your namespace — a live reference handoff, not a copy — and needs the owner token, because it
writes to state you own.

``` python
sess.run('df_norm = df.assign(x_norm=(df.x - df.x.min()) / (df.x.max() - df.x.min()))')
print(httpx.get(f'{url}/agent/api/rows').json()['agent_names'])
import json as _json
httpx.get(f'{url}/api/promote', params={'accessor': _json.dumps(['df_norm']), 'token': tok}).json()
```

    ['col', 'df_norm', 'max_val', 'min_val', 'np', 'numeric_cols']

    {'ok': True, 'error': None}

``` python
'df_norm' in globals()   # now it is yours, and any frontend watching this process sees it
```

    True

## Learn more

The [documentation](https://vedicreader.github.io/dhrishti/) covers the inspection core, the
runtime, the serving layer and the agent overlay in full.

dhrishti is built with [nbdev](https://nbdev.fast.ai): edit the notebooks under `nbs/`, then
`nbdev-prepare` to compile the library, run the tests, and rebuild the docs.
