Skip to content

Commit ab36e6b

Browse files
committed
R4: Build Dhyanesh orchestration, simulation and release layer
Implement TinyAgent.run as a bounded, single-decision lifecycle. It uses Aisha's frozen types, registry and prompt contract; limits model calls to 1 + max_repairs; repairs from the original prompt; never retries backend errors or terminal tool failures; and preserves answer provenance and raw model outputs in AgentResult. Add deterministic SimulatedBackend, JSONL RecordingBackend and SHA-256 prompt-matched ReplayBackend. Add 35 ground-truth scenarios, the F01-F16 fault taxonomy, round-trip validation, repair and exhaustion variants, false-execution checks, and simulated/live/replay evaluation commands. Provide 50 labelled live queries and explicitly mark unrun measurements as "not executed" instead of publishing estimated accuracy. Add the public exports, CLI with a labelled simulated mode, examples, packaging, README, Python 3.10/3.12 CI, dependency and dynamic-execution AST gates, and Dhyanesh's integration tests. Ignore prd/ and generated artifacts so planning documents cannot enter this branch. This branch is based on Aisha's existing aisha branch. Her reflection, registry, prompt, contract files and tests remain unchanged. The new simulator passes their contract integration check. The full fault eval, live runtimes and CLI execution remain pending on the parser, executor, coercion and transport modules from the other owners; CI labels the simulated eval deferred until those modules exist. PRD-04 demo fixtures are kept separate because Aisha's search and set_alarm test signatures differ from the PRD-04 scenario contract. Verified: 31 unit tests pass; wheel builds with no runtime dependencies; staged diff passes whitespace checks; prd/ matches .gitignore.
1 parent e0d619e commit ab36e6b

25 files changed

Lines changed: 1954 additions & 1 deletion

‎.github/workflows/ci.yml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
on:
3+
push:
4+
pull_request:
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: ["3.10", "3.12"]
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: ${{ matrix.python-version }}
16+
- run: python -m pip install .
17+
- run: python -m unittest discover -s tests -v
18+
- name: Simulated evaluation
19+
run: |
20+
if [ -f tinyagent/parser.py ] && [ -f tinyagent/executor.py ] && [ -f tinyagent/coercion.py ]; then
21+
python -m eval.run_eval --mode sim
22+
else
23+
echo "Simulated evaluation deferred: parser, executor, or coercion module pending"
24+
fi

‎.gitignore‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
prd/
2+
__pycache__/
3+
*.py[cod]
4+
*.egg-info/
5+
.venv/
6+
build/
7+
dist/

‎ARCHITECTURE.md‎

Lines changed: 676 additions & 0 deletions
Large diffs are not rendered by default.

‎README.md‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# TinyAgent
2+
3+
TinyAgent routes one query to one registered Python tool through a local language
4+
model, with at most `1 + max_repairs` model calls. Its architecture and tradeoffs
5+
are documented in [ARCHITECTURE.md](ARCHITECTURE.md).
6+
7+
Aisha's reflection, registry, prompt, and frozen contract modules are integrated.
8+
The transport, parser, and executor modules are owned by the other engineers
9+
and have not yet landed. The CLI and full evaluation require the parser and
10+
executor. The evaluation uses the PRD-04 fixture signatures in
11+
`examples/demo_tools.py`; Aisha's `tests/fixtures_tools.py` has different
12+
`search` and `set_alarm` signatures.
13+
14+
## Quickstart
15+
16+
```python
17+
from examples.demo_tools import build_registry
18+
from tinyagent import TinyAgent
19+
from tinyagent.backends.simulator import SimulatedBackend
20+
21+
backend = SimulatedBackend(['ACTION: add(a="2", b="3")'])
22+
agent = TinyAgent(backend, build_registry())
23+
result = agent.run("What is 2+3?")
24+
print(f"{result.text} (provenance: {result.provenance})")
25+
```
26+
27+
After the other modules land, run `python -m tinyagent --backend sim`.
28+
The CLI labels this mode `[SIMULATED]` and prints provenance next to every
29+
answer. A `FINAL:` answer has `provenance=model`; it is not verified.
30+
31+
## Supported runtimes
32+
33+
The planned live backends are Ollama and llama.cpp server. Tested runtime
34+
versions will be added after Deepak's transport handoff and a live run.
35+
Use `python -m tinyagent --backend ollama --model llama3:8b "What is 2+3?"`
36+
or `--backend llamacpp` when those modules are available.
37+
38+
## Evaluation
39+
40+
`python -m eval.run_eval --mode sim` runs the seeded fault corpus without a GPU
41+
once the parser and executor are available. The latest
42+
[evaluation report](eval/report.md) currently marks both simulated and live
43+
runs as not executed. No live accuracy is claimed until a live run is recorded.
44+
45+
Known limits: one tool per query; no conversation memory, tool chaining, or
46+
complex argument types. A valid but wrong tool choice cannot be detected by the
47+
parser. Registered tools must enforce their own authorization and timeouts;
48+
the executor cannot kill a tool thread after timeout.

‎eval/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Offline and live TinyAgent evaluation."""

‎eval/faults.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Forward-generated faults from PRD-03 Section 7.
2+
3+
The verdict table requires co-review by the parser owner before release.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import json
9+
import re
10+
from dataclasses import dataclass
11+
from typing import Callable
12+
13+
from eval.scenarios import Scenario
14+
15+
16+
@dataclass(frozen=True)
17+
class Fault:
18+
id: str
19+
verdict: str
20+
expected_code: str | None
21+
corrupt: Callable[[Scenario], str]
22+
23+
24+
def _replace_first_arg(s: Scenario, name: str, value: str | None = None) -> str:
25+
first = next(iter(s.args))
26+
old = f'{first}="{s.args[first]}"'
27+
if value is None:
28+
return s.canonical_output.replace(old, f'{name}="{s.args[first]}"', 1)
29+
return s.canonical_output.replace(old, f'{name}="{value}"', 1)
30+
31+
32+
def _positional(s: Scenario) -> str:
33+
values = ", ".join(f'"{value}"' for value in s.args.values())
34+
return f"ACTION: {s.tool}({values})"
35+
36+
37+
def _drop_required(s: Scenario) -> str:
38+
first = next(iter(s.args))
39+
parts = [f'{key}="{value}"' for key, value in s.args.items() if key != first]
40+
return f'ACTION: {s.tool}({", ".join(parts)})'
41+
42+
43+
def _bad_int(s: Scenario) -> str:
44+
name = next(key for key in s.args if key in {"a", "b", "limit", "hour", "minute"})
45+
return s.canonical_output.replace(f'{name}="{s.args[name]}"', f'{name}="two"', 1)
46+
47+
48+
FAULTS = (
49+
Fault("F01", "recover", None, lambda s: f"```\n{s.canonical_output}\n```"),
50+
Fault("F02", "recover", None, lambda s: f"Here is the answer:\n{s.canonical_output}"),
51+
Fault("F03", "recover", None, lambda s: f"{s.canonical_output}\nThat is all."),
52+
Fault("F04", "recover", None, lambda s: s.canonical_output.replace("ACTION:", "action:", 1)),
53+
Fault("F05", "recover", None, lambda s: s.canonical_output.replace('"', "'")),
54+
Fault("F06", "recover", None, lambda s: re.sub(r'="([+-]?\d+)"', r'=\1', s.canonical_output)),
55+
Fault("F07", "recover", None, _positional),
56+
Fault("F08", "recover", None, lambda s: s.canonical_output[:-1] + ",)"),
57+
Fault("F09", "repair", "E_NO_PREFIX", lambda s: json.dumps({"tool": s.tool, "args": s.args})),
58+
Fault("F10", "repair", "E_BAD_SYNTAX", lambda s: s.canonical_output[:-1]),
59+
Fault("F11", "repair", "E_UNKNOWN_TOOL", lambda s: s.canonical_output.replace(
60+
f"ACTION: {s.tool}(", f"ACTION: {s.tool}_numbers(", 1)),
61+
Fault("F12", "repair", "E_UNEXPECTED_ARG", lambda s: _replace_first_arg(s, "x")),
62+
Fault("F13", "repair", "E_MISSING_ARG", _drop_required),
63+
Fault("F14", "repair", "E_TYPE_COERCION", _bad_int),
64+
Fault("F15", "repair", "E_EMPTY", lambda s: ""),
65+
Fault("F16", "reject", "E_TOOL_EXCEPTION", lambda s: "ACTION: boom()"),
66+
)

‎eval/fixtures/README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Replay fixtures
2+
3+
Recordings contain full prompts and model outputs. Commit only recordings
4+
generated from `eval/live_queries.jsonl`; keep user-data recordings outside
5+
this directory and out of version control.

‎eval/live_queries.jsonl‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{"query":"Add 2 and 3","tool":"add","args":{"a":2,"b":3}}
2+
{"query":"Add 0 and 0","tool":"add","args":{"a":0,"b":0}}
3+
{"query":"Add 4 and 9","tool":"add","args":{"a":4,"b":9}}
4+
{"query":"Add 17 and 25","tool":"add","args":{"a":17,"b":25}}
5+
{"query":"Add 100 and 1","tool":"add","args":{"a":100,"b":1}}
6+
{"query":"Add 8 and 13","tool":"add","args":{"a":8,"b":13}}
7+
{"query":"Add 12 and 5","tool":"add","args":{"a":12,"b":5}}
8+
{"query":"Add 42 and 58","tool":"add","args":{"a":42,"b":58}}
9+
{"query":"Add 1 and 999","tool":"add","args":{"a":1,"b":999}}
10+
{"query":"Add 123 and 456","tool":"add","args":{"a":123,"b":456}}
11+
{"query":"What is 6 plus 7?","tool":"add","args":{"a":6,"b":7}}
12+
{"query":"What is 30 plus 40?","tool":"add","args":{"a":30,"b":40}}
13+
{"query":"Sum 5 and 10","tool":"add","args":{"a":5,"b":10}}
14+
{"query":"Sum 99 and 1","tool":"add","args":{"a":99,"b":1}}
15+
{"query":"Compute 11 + 22","tool":"add","args":{"a":11,"b":22}}
16+
{"query":"Get weather in Pune in c","tool":"get_weather","args":{"city":"Pune","unit":"c"}}
17+
{"query":"Get weather in Delhi in c","tool":"get_weather","args":{"city":"Delhi","unit":"c"}}
18+
{"query":"Get weather in Mumbai in c","tool":"get_weather","args":{"city":"Mumbai","unit":"c"}}
19+
{"query":"Get weather in Chennai in c","tool":"get_weather","args":{"city":"Chennai","unit":"c"}}
20+
{"query":"Get weather in Kolkata in c","tool":"get_weather","args":{"city":"Kolkata","unit":"c"}}
21+
{"query":"Weather for Jaipur, c units","tool":"get_weather","args":{"city":"Jaipur","unit":"c"}}
22+
{"query":"Weather for Hyderabad, c units","tool":"get_weather","args":{"city":"Hyderabad","unit":"c"}}
23+
{"query":"Weather for Bengaluru, c units","tool":"get_weather","args":{"city":"Bengaluru","unit":"c"}}
24+
{"query":"Weather for Nagpur, c units","tool":"get_weather","args":{"city":"Nagpur","unit":"c"}}
25+
{"query":"Weather for Surat, c units","tool":"get_weather","args":{"city":"Surat","unit":"c"}}
26+
{"query":"Search Python, limit 1","tool":"search","args":{"q":"Python","limit":1}}
27+
{"query":"Search agents, limit 2","tool":"search","args":{"q":"agents","limit":2}}
28+
{"query":"Search Pune, limit 3","tool":"search","args":{"q":"Pune","limit":3}}
29+
{"query":"Search solar, limit 4","tool":"search","args":{"q":"solar","limit":4}}
30+
{"query":"Search maps, limit 5","tool":"search","args":{"q":"maps","limit":5}}
31+
{"query":"Search books, limit 6","tool":"search","args":{"q":"books","limit":6}}
32+
{"query":"Search trains, limit 7","tool":"search","args":{"q":"trains","limit":7}}
33+
{"query":"Search music, limit 8","tool":"search","args":{"q":"music","limit":8}}
34+
{"query":"Search science, limit 9","tool":"search","args":{"q":"science","limit":9}}
35+
{"query":"Search recipes, limit 10","tool":"search","args":{"q":"recipes","limit":10}}
36+
{"query":"Set alarm for 1:11 labelled event1","tool":"set_alarm","args":{"hour":1,"minute":11,"label":"event1"}}
37+
{"query":"Set alarm for 2:12 labelled event2","tool":"set_alarm","args":{"hour":2,"minute":12,"label":"event2"}}
38+
{"query":"Set alarm for 3:13 labelled event3","tool":"set_alarm","args":{"hour":3,"minute":13,"label":"event3"}}
39+
{"query":"Set alarm for 4:14 labelled event4","tool":"set_alarm","args":{"hour":4,"minute":14,"label":"event4"}}
40+
{"query":"Set alarm for 5:15 labelled event5","tool":"set_alarm","args":{"hour":5,"minute":15,"label":"event5"}}
41+
{"query":"Set alarm for 6:16 labelled event6","tool":"set_alarm","args":{"hour":6,"minute":16,"label":"event6"}}
42+
{"query":"Set alarm for 7:17 labelled event7","tool":"set_alarm","args":{"hour":7,"minute":17,"label":"event7"}}
43+
{"query":"Set alarm for 8:18 labelled event8","tool":"set_alarm","args":{"hour":8,"minute":18,"label":"event8"}}
44+
{"query":"Set alarm for 9:19 labelled event9","tool":"set_alarm","args":{"hour":9,"minute":19,"label":"event9"}}
45+
{"query":"Set alarm for 10:20 labelled event10","tool":"set_alarm","args":{"hour":10,"minute":20,"label":"event10"}}
46+
{"query":"Set alarm for 11:21 labelled event11","tool":"set_alarm","args":{"hour":11,"minute":21,"label":"event11"}}
47+
{"query":"Set alarm for 12:22 labelled event12","tool":"set_alarm","args":{"hour":12,"minute":22,"label":"event12"}}
48+
{"query":"Set alarm for 13:23 labelled event13","tool":"set_alarm","args":{"hour":13,"minute":23,"label":"event13"}}
49+
{"query":"Set alarm for 14:24 labelled event14","tool":"set_alarm","args":{"hour":14,"minute":24,"label":"event14"}}
50+
{"query":"Set alarm for 15:25 labelled event15","tool":"set_alarm","args":{"hour":15,"minute":25,"label":"event15"}}

‎eval/report.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"simulated": "not executed",
3+
"simulated_reason": "collaborator modules pending",
4+
"live": "not executed"
5+
}

‎eval/report.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# TinyAgent evaluation
2+
3+
| Metric | Value |
4+
|---|---|
5+
| Simulated evaluation | not executed; collaborator modules pending |
6+
| Live evaluation | not executed |

0 commit comments

Comments
 (0)