# agent


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

`author(concept, ask)` asks a model for one demo on a concept. The model
writes data only: a JSON spec. It never writes code. Then the loop
checks the spec, and gives the errors back to the model to fix:

1.  Parse the reply as JSON.
2.  [`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
    the spec: types, binds, gates and the think rule.
3.  [`check`](https://vedicreader.github.io/rupaka/check.html#check) the
    spec in headless Chrome: it draws, it plays, the keys work, the
    contrast is good.
4.  On errors, ask for a repair with only these errors. At most two
    repairs.
5.  Give up quietly: return `None` and log the errors. The caller keeps
    its text lesson.

`ask(prompt, sp)` is any function that returns text, as in abhyasa. The
tests use a fake one.

## The catalog

The prompt lists the blocks and fns from their manifests, so it never
names a block that the runtime does not have.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L20"
target="_blank" style="float:right; font-size:smaller">source</a>

### catalog

``` python
def catalog()->str:
```

*The blocks and fns, with their purpose and props, as text for the
prompt.*

The catalog names every block and every fn.

``` python
cat = catalog()
for n in [*manifests(), *fn_manifests()]: assert f'- {n}: ' in cat, n
print(cat[:600])
```

## The system prompt

The system prompt gives the rules of a good demo, the shape of a spec,
and the catalog.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L36"
target="_blank" style="float:right; font-size:smaller">source</a>

### demo_sp

``` python
def demo_sp()->str:
```

*The system prompt of the demo agent.*

``` python
assert 'You never write code' in demo_sp() and '- wheel: ' in demo_sp()
```

## The user prompt

The user prompt describes the concept, the first error of the learner
(if there is one), and two gallery demos as examples.
[`near`](https://vedicreader.github.io/rupaka/agent.html#near) picks the
gallery demos that share the most words with the concept. A long table
in an example is cut to three rows.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L59"
target="_blank" style="float:right; font-size:smaller">source</a>

### near

``` python
def near(
    concept, k:int=2
)->list:
```

*The `k` gallery demos that share the most words with `concept`.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L70"
target="_blank" style="float:right; font-size:smaller">source</a>

### demo_prompt

``` python
def demo_prompt(
    concept, first_error:str='', examples:tuple=()
)->str:
```

*The prompt that asks for a demo on `concept`, a dict with name,
statement, subject and misconceptions.*

``` python
c = dict(name='Reciprocal rank fusion', statement='Sum 1/(k + rank) over the lists.', subject='ml', misconceptions=['raw scores add'])
p = demo_prompt(c, 'You add the raw scores.', near(c))
assert 'first error: You add the raw scores.' in p and '"id": "reciprocal-rank-fusion"' in p
```

## The repair prompt

The repair prompt gives back the spec and only its errors.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L83"
target="_blank" style="float:right; font-size:smaller">source</a>

### repair_prompt

``` python
def repair_prompt(
    spec, errors
)->str:
```

*The prompt that asks the model to fix only `errors` in `spec`.*

## Parse

[`parse_json`](https://vedicreader.github.io/rupaka/agent.html#parse_json)
finds the JSON object in a reply, also inside a fence or after some
text.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L90"
target="_blank" style="float:right; font-size:smaller">source</a>

### parse_json

``` python
def parse_json(
    s:str
)->dict:
```

*The first JSON object in `s`. Raise `ValueError` when there is none.*

``` python
test_eq(parse_json('Here:\n```json\n{"a": 1}\n```'), {'a': 1})
test_fail(lambda: parse_json('no json'), contains='no JSON')
```

## author

[`author`](https://vedicreader.github.io/rupaka/agent.html#author) runs
the loop. It stamps the spec with the schema, an id and the concept
slug. `log`, when given, gets one entry for each failed try.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/rupaka/blob/main/rupaka/agent.py#L105"
target="_blank" style="float:right; font-size:smaller">source</a>

### author

``` python
def author(
    concept, ask, first_error:str='', examples:NoneType=None, check:bool=True, repairs:int=2, log:list=None
)->dict | None:
```

*A checked demo spec on `concept` from the model `ask`, or None after
`repairs` failed repairs.*

### Tests with a fake model

`fake` replies from a list, one reply for each call, and keeps the
prompts.

``` python
from rupaka.spec import examples as block_examples
```

``` python
def fake(*replies):
    calls = []
    def ask(p, sp=''): calls.append(p); return replies[min(len(calls), len(replies)) - 1]
    ask.calls = calls
    return ask
good = json.dumps(block_examples()['gate'])
```

A good spec passes on the first call. The spec gets the concept slug.

``` python
a = fake(good)
s = author(dict(name='Sums', slug='sums'), a, check=False)
test_eq((s['concept'], len(a.calls)), ('sums', 1))
```

A spec with one error gets one repair. The repair prompt names the
error.

``` python
broken = json.loads(good); del broken['blocks'][1]['after']
a, log = fake(json.dumps(broken), good), []
s = author('Sums', a, check=False, log=log)
assert s is not None and len(a.calls) == 2
assert 'gives away the answer of gate "sum"' in a.calls[1] and 'Fix only these errors' in a.calls[1]
test_eq(log[0]['tries'], 1)
```

Three bad replies give up quietly: the result is `None`, and the log has
three entries.

``` python
log = []
test_eq(author('Sums', fake('not json', json.dumps(broken), '{}'), check=False, log=log), None)
test_eq(len(log), 3)
```

A model that fails gives up too, with no exception.

``` python
def down(p, sp=''): raise ConnectionError('no network')
test_eq(author('Sums', down, check=False), None)
```

With a browser,
[`check`](https://vedicreader.github.io/rupaka/check.html#check) runs
too, and a good spec still passes in one call.

``` python
from rupaka.check import can_check
```

``` python
if can_check(): assert author('Sums', fake(good)) is not None
```

## A live run

A live run uses a real model, so it is not part of the tests. With the
`rishi` package and its model `claude/sonnet`, it looks like this:

``` python
from rishi import Chat, resp_text
def ask(p, sp=''):
    chat = Chat('claude/sonnet', sp=sp)
    try: return resp_text(chat(p))
    finally: chat.close()
log = []
spec = author(dict(name='Guṇa sandhi', statement='a or ā before i, u or ṛ gives e, o or ar.', subject='sanskrit'), ask, log=log)
```

The first live run (26 October 2026, `claude/sonnet`, three concepts:
guṇa sandhi, cosine similarity and binary search):

<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 33%" />
<col style="width: 33%" />
</colgroup>
<thead>
<tr>
<th>concept</th>
<th>result</th>
<th>model calls</th>
</tr>
</thead>
<tbody>
<tr>
<td>guṇa sandhi</td>
<td>passed on the first try</td>
<td>1</td>
</tr>
<tr>
<td>cosine similarity</td>
<td>passed after one repair: a point showed the answer before the gate,
and the think rule caught it</td>
<td>2</td>
</tr>
<tr>
<td>binary search</td>
<td>passed on the first try</td>
<td>1</td>
</tr>
</tbody>
</table>

So 3 of 3 passed, 2 of them on the first try. One spec put `{n}` in its
plain-text `ask`. The check could not see it, so
[`validate`](https://vedicreader.github.io/rupaka/spec.html#validate)
now refuses a template in a plain-text prop.
