pyacri 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyacri-0.4.0/.github/ISSUE_TEMPLATE/bug_report.md +22 -0
- pyacri-0.4.0/.github/ISSUE_TEMPLATE/design_challenge.md +17 -0
- pyacri-0.4.0/.github/ISSUE_TEMPLATE/proposal.md +13 -0
- pyacri-0.4.0/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- pyacri-0.4.0/.github/workflows/ci.yml +72 -0
- pyacri-0.4.0/.github/workflows/publish.yml +38 -0
- pyacri-0.4.0/.gitignore +41 -0
- pyacri-0.4.0/CODE_OF_CONDUCT.md +39 -0
- pyacri-0.4.0/CONTRIBUTING.md +59 -0
- pyacri-0.4.0/LICENSE +21 -0
- pyacri-0.4.0/PKG-INFO +296 -0
- pyacri-0.4.0/README.md +259 -0
- pyacri-0.4.0/SECURITY.md +34 -0
- pyacri-0.4.0/acri/__init__.py +73 -0
- pyacri-0.4.0/acri/_synonyms.py +31 -0
- pyacri-0.4.0/acri/_template.py +27 -0
- pyacri-0.4.0/acri/_text.py +34 -0
- pyacri-0.4.0/acri/adapters.py +50 -0
- pyacri-0.4.0/acri/cli.py +97 -0
- pyacri-0.4.0/acri/compass.py +70 -0
- pyacri-0.4.0/acri/config.py +87 -0
- pyacri-0.4.0/acri/corpus.py +57 -0
- pyacri-0.4.0/acri/credentials.py +25 -0
- pyacri-0.4.0/acri/daemon.py +75 -0
- pyacri-0.4.0/acri/escape_hatch.py +38 -0
- pyacri-0.4.0/acri/gate.py +42 -0
- pyacri-0.4.0/acri/ledger.py +59 -0
- pyacri-0.4.0/acri/mcp_connect.py +45 -0
- pyacri-0.4.0/acri/port.py +69 -0
- pyacri-0.4.0/acri/press.py +64 -0
- pyacri-0.4.0/acri/py.typed +0 -0
- pyacri-0.4.0/acri/router.py +21 -0
- pyacri-0.4.0/acri/sandbox.py +43 -0
- pyacri-0.4.0/acri/schemas.py +34 -0
- pyacri-0.4.0/acri/server.py +87 -0
- pyacri-0.4.0/acri/studio.py +70 -0
- pyacri-0.4.0/acri/studio_data.py +38 -0
- pyacri-0.4.0/acri/studio_page.html +112 -0
- pyacri-0.4.0/assay/accuracy.py +75 -0
- pyacri-0.4.0/assay/clients.py +32 -0
- pyacri-0.4.0/assay/diagnose.py +63 -0
- pyacri-0.4.0/assay/fixtures.json +177 -0
- pyacri-0.4.0/assay/fixtures.py +41 -0
- pyacri-0.4.0/assay/fixtures_500.json +1 -0
- pyacri-0.4.0/assay/latency.py +43 -0
- pyacri-0.4.0/assay/mcp_live.py +63 -0
- pyacri-0.4.0/assay/recall.py +49 -0
- pyacri-0.4.0/assay/report.py +18 -0
- pyacri-0.4.0/assay/scale.py +64 -0
- pyacri-0.4.0/docs/architecture.md +289 -0
- pyacri-0.4.0/docs/assets/benchmark-results.svg +59 -0
- pyacri-0.4.0/docs/assets/resolution-flow.svg +59 -0
- pyacri-0.4.0/docs/decisions.md +535 -0
- pyacri-0.4.0/examples/live_demo.py +60 -0
- pyacri-0.4.0/pyproject.toml +53 -0
- pyacri-0.4.0/rust/.gitignore +2 -0
- pyacri-0.4.0/rust/Cargo.toml +8 -0
- pyacri-0.4.0/rust/README.md +33 -0
- pyacri-0.4.0/rust/src/bm25.rs +28 -0
- pyacri-0.4.0/rust/src/compass.rs +45 -0
- pyacri-0.4.0/rust/src/corpus.rs +72 -0
- pyacri-0.4.0/rust/src/lib.rs +19 -0
- pyacri-0.4.0/rust/src/synonyms.rs +43 -0
- pyacri-0.4.0/rust/src/text.rs +47 -0
- pyacri-0.4.0/rust/tests/resolve.rs +61 -0
- pyacri-0.4.0/tests/test_assay_smoke.py +51 -0
- pyacri-0.4.0/tests/test_cli.py +32 -0
- pyacri-0.4.0/tests/test_compass.py +37 -0
- pyacri-0.4.0/tests/test_config.py +95 -0
- pyacri-0.4.0/tests/test_corpus.py +40 -0
- pyacri-0.4.0/tests/test_daemon.py +59 -0
- pyacri-0.4.0/tests/test_escape_hatch.py +25 -0
- pyacri-0.4.0/tests/test_gate.py +34 -0
- pyacri-0.4.0/tests/test_integration.py +89 -0
- pyacri-0.4.0/tests/test_ports.py +96 -0
- pyacri-0.4.0/tests/test_press.py +35 -0
- pyacri-0.4.0/tests/test_router.py +58 -0
- pyacri-0.4.0/tests/test_sandbox.py +34 -0
- pyacri-0.4.0/tests/test_schemas.py +46 -0
- pyacri-0.4.0/tests/test_server.py +55 -0
- pyacri-0.4.0/tests/test_studio.py +83 -0
- pyacri-0.4.0/tests/test_studio_data.py +55 -0
- pyacri-0.4.0/typescript/.gitignore +2 -0
- pyacri-0.4.0/typescript/README.md +42 -0
- pyacri-0.4.0/typescript/package-lock.json +47 -0
- pyacri-0.4.0/typescript/package.json +15 -0
- pyacri-0.4.0/typescript/src/compass.ts +52 -0
- pyacri-0.4.0/typescript/src/corpus.ts +37 -0
- pyacri-0.4.0/typescript/src/synonyms.ts +27 -0
- pyacri-0.4.0/typescript/src/text.ts +28 -0
- pyacri-0.4.0/typescript/test/compass.test.ts +45 -0
- pyacri-0.4.0/typescript/tsconfig.json +11 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: acri resolved the wrong tools, or misbehaved
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**What happened**
|
|
8
|
+
|
|
9
|
+
**What you expected**
|
|
10
|
+
|
|
11
|
+
**Steps to reproduce**
|
|
12
|
+
1.
|
|
13
|
+
|
|
14
|
+
**Environment**
|
|
15
|
+
- acri version:
|
|
16
|
+
- Provider + model (gemini / openai-compatible / anthropic / local):
|
|
17
|
+
- Number of registered tools:
|
|
18
|
+
- OS + Python/Node version:
|
|
19
|
+
|
|
20
|
+
**`ledger` output for the failing turn**
|
|
21
|
+
|
|
22
|
+
<!-- The trace is usually the whole answer. Redact arguments if they are sensitive. -->
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Design challenge
|
|
3
|
+
about: Argue that part of the architecture is wrong
|
|
4
|
+
labels: design
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Which part** (corpus / compass / port / gate / press / ledger / assay, or a claim in docs/architecture.md)
|
|
8
|
+
|
|
9
|
+
**What the design currently says**
|
|
10
|
+
|
|
11
|
+
**Why it is wrong**
|
|
12
|
+
|
|
13
|
+
<!-- Evidence beats intuition: a paper, a provider doc, an assay run, or arithmetic. -->
|
|
14
|
+
|
|
15
|
+
**What it should be instead**
|
|
16
|
+
|
|
17
|
+
**What this would cost** (what breaks, what has to be rewritten, who is affected)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Proposal
|
|
3
|
+
about: Propose a new port adapter, corpus ingester, or assay benchmark
|
|
4
|
+
labels: proposal
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Surface** (port adapter / corpus ingester / assay benchmark / compass ranking)
|
|
8
|
+
|
|
9
|
+
**What it adds** — one paragraph. What can a user do afterwards that they cannot do now?
|
|
10
|
+
|
|
11
|
+
**Why it is the minimal version** — what did you deliberately leave out?
|
|
12
|
+
|
|
13
|
+
**How it is proven** — which `assay` run shows it works, or what run would need to exist?
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## What
|
|
2
|
+
|
|
3
|
+
## Why this is the minimal version
|
|
4
|
+
|
|
5
|
+
What did you deliberately *not* build?
|
|
6
|
+
|
|
7
|
+
## Checklist
|
|
8
|
+
- [ ] No performance number without a reproducible `assay/` script and a committed run
|
|
9
|
+
- [ ] Nothing rewrites a prompt prefix (or the arithmetic showing it wins is included)
|
|
10
|
+
- [ ] `gate` stays advisory — no path suppresses a tool call the model wanted
|
|
11
|
+
- [ ] Every resolve decision still reaches `ledger`
|
|
12
|
+
- [ ] No new dependency that a few lines could replace
|
|
13
|
+
- [ ] No daemon, gateway, or port introduced
|
|
14
|
+
- [ ] Code files ≤ 250 words (350 hard cap); docs exempt
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push: { branches: [main] }
|
|
4
|
+
pull_request:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
test:
|
|
8
|
+
name: pytest
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
python-version: ["3.10", "3.12"]
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with: { python-version: "${{ matrix.python-version }}" }
|
|
17
|
+
- run: pip install -e ".[dev]"
|
|
18
|
+
- run: pytest -q
|
|
19
|
+
|
|
20
|
+
claims:
|
|
21
|
+
name: no unreceipted claims
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with: { python-version: "3.12" }
|
|
27
|
+
|
|
28
|
+
- name: Required community files exist
|
|
29
|
+
run: |
|
|
30
|
+
for f in README.md LICENSE CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md docs/architecture.md; do
|
|
31
|
+
test -f "$f" || { echo "missing: $f"; exit 1; }
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
- name: Every performance claim carries its receipt
|
|
35
|
+
run: |
|
|
36
|
+
python - <<'PY'
|
|
37
|
+
import pathlib, re, sys
|
|
38
|
+
# A performance claim is a percentage or an "Nx better" multiplier.
|
|
39
|
+
claim = re.compile(r'\d+(?:\.\d+)?\s*%'
|
|
40
|
+
r'|\d+(?:\.\d+)?\s*[x×]\s*(?:faster|cheaper|smaller|less|lower|'
|
|
41
|
+
r'reduction|improvement|better|speedup)', re.I)
|
|
42
|
+
# A receipt is a citation, an assay run, or an explicit hedge.
|
|
43
|
+
receipt = re.compile(r'https?://|assay/|estimate|projected|hypothetical|illustrative|'
|
|
44
|
+
r'worked example|for example|e\.g\.', re.I)
|
|
45
|
+
bad = []
|
|
46
|
+
for pattern in ('*.md', '*.svg', '*.html'):
|
|
47
|
+
for p in pathlib.Path('.').rglob(pattern):
|
|
48
|
+
if '.github' in p.parts: continue
|
|
49
|
+
for n, line in enumerate(p.read_text(encoding='utf-8').splitlines(), 1):
|
|
50
|
+
if claim.search(line) and not receipt.search(line):
|
|
51
|
+
bad.append(f"{p}:{n}: {line.strip()[:100]}")
|
|
52
|
+
if bad:
|
|
53
|
+
print("Performance claims without a citation, an assay run, or an explicit hedge.")
|
|
54
|
+
print("See the claims policy in README.md.\n")
|
|
55
|
+
print("\n".join(bad))
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
print("ok - no unreceipted claims")
|
|
58
|
+
PY
|
|
59
|
+
|
|
60
|
+
- name: Relative links resolve
|
|
61
|
+
run: |
|
|
62
|
+
python - <<'PY'
|
|
63
|
+
import pathlib, re, sys
|
|
64
|
+
bad = []
|
|
65
|
+
for p in pathlib.Path('.').rglob('*.md'):
|
|
66
|
+
for target in re.findall(r'\]\(([^)#?:]+)\)', p.read_text(encoding='utf-8')):
|
|
67
|
+
t = target.strip()
|
|
68
|
+
if t.startswith(('http', '#', 'mailto:', '../../')): continue
|
|
69
|
+
if not (p.parent / t).exists(): bad.append(f"{p} -> {t}")
|
|
70
|
+
if bad: sys.exit("broken relative links:\n" + "\n".join(bad))
|
|
71
|
+
print("ok - links resolve")
|
|
72
|
+
PY
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Triggered only by publishing a GitHub Release -- pushing a tag alone does
|
|
4
|
+
# NOT run this. A release is a separate, deliberate action so there's always
|
|
5
|
+
# a human decision point between "tag exists" and "PyPI gets a new version",
|
|
6
|
+
# which cannot be undone once uploaded (only yanked, never overwritten).
|
|
7
|
+
#
|
|
8
|
+
# Needs, on the PyPI side, a trusted publisher registered for this repo
|
|
9
|
+
# (Publishing -> Trusted Publishers on the PyPI project page): owner
|
|
10
|
+
# INERATE, repo acri, workflow publish.yml, environment pypi. That's a
|
|
11
|
+
# one-time setup step on pypi.org itself -- nothing here can do it, and
|
|
12
|
+
# nothing here runs until it's done and a Release is published.
|
|
13
|
+
on:
|
|
14
|
+
release:
|
|
15
|
+
types: [published]
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with: { python-version: "3.12" }
|
|
24
|
+
- run: pip install build
|
|
25
|
+
- run: python -m build
|
|
26
|
+
- uses: actions/upload-artifact@v4
|
|
27
|
+
with: { name: dist, path: dist/ }
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write # OIDC trusted publishing -- no stored token/secret
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with: { name: dist, path: dist/ }
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
pyacri-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# environments & secrets
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.pyc
|
|
9
|
+
.venv/
|
|
10
|
+
dist/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# rust
|
|
14
|
+
target/
|
|
15
|
+
rust/target/
|
|
16
|
+
**/*.rs.bk
|
|
17
|
+
**/.rustc_info.json
|
|
18
|
+
**/.cargo-artifact-lock
|
|
19
|
+
**/.cargo-build-lock
|
|
20
|
+
**/.cargo-lock
|
|
21
|
+
**/CACHEDIR.TAG
|
|
22
|
+
|
|
23
|
+
# node & typescript
|
|
24
|
+
node_modules/
|
|
25
|
+
typescript/node_modules/
|
|
26
|
+
typescript/dist/
|
|
27
|
+
*.tsbuildinfo
|
|
28
|
+
|
|
29
|
+
# local indexes & traces (never committed)
|
|
30
|
+
*.acri-index
|
|
31
|
+
ledger/*.jsonl
|
|
32
|
+
.acri/
|
|
33
|
+
assay/results/
|
|
34
|
+
|
|
35
|
+
# junk
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
|
|
39
|
+
# private working notes — never published
|
|
40
|
+
CLAUDE.md
|
|
41
|
+
.private/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and maintainers pledge to make participation in acri a
|
|
6
|
+
harassment-free experience for everyone, regardless of age, body size, disability,
|
|
7
|
+
ethnicity, gender identity and expression, level of experience, education, socio-economic
|
|
8
|
+
status, nationality, personal appearance, race, religion, or sexual identity and
|
|
9
|
+
orientation.
|
|
10
|
+
|
|
11
|
+
## Our standards
|
|
12
|
+
|
|
13
|
+
**Do:** be kind and patient (many contributors are not native English speakers, and many
|
|
14
|
+
are learning systems engineering as they go — that is welcome here), give constructive
|
|
15
|
+
review comments, accept constructive criticism gracefully, focus on what is best for the
|
|
16
|
+
community.
|
|
17
|
+
|
|
18
|
+
**Don't:** use sexualized language or imagery, troll, insult, make personal or political
|
|
19
|
+
attacks, harass publicly or privately, publish others' private information, or behave in
|
|
20
|
+
ways that would be considered inappropriate in a professional setting.
|
|
21
|
+
|
|
22
|
+
## Technical disagreement is not misconduct
|
|
23
|
+
|
|
24
|
+
Arguing that a design is wrong — bluntly, with evidence — is encouraged and is how this
|
|
25
|
+
project improves. Attacking the person who proposed it is not. The line is whether you
|
|
26
|
+
are addressing the argument or the author.
|
|
27
|
+
|
|
28
|
+
## Enforcement
|
|
29
|
+
|
|
30
|
+
Maintainers are responsible for clarifying standards and will take appropriate, fair
|
|
31
|
+
corrective action in response to unacceptable behavior — from a warning to a permanent
|
|
32
|
+
ban depending on severity and repetition.
|
|
33
|
+
|
|
34
|
+
Report violations by opening a confidential issue or contacting the maintainers. All
|
|
35
|
+
reports will be reviewed promptly and kept confidential.
|
|
36
|
+
|
|
37
|
+
## Attribution
|
|
38
|
+
|
|
39
|
+
Adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Contributing to acri
|
|
2
|
+
|
|
3
|
+
acri is a small, sharp library that refuses to grow into a framework. Contributions are
|
|
4
|
+
held to that standard — the constraint is the product.
|
|
5
|
+
|
|
6
|
+
## Ground rules
|
|
7
|
+
|
|
8
|
+
1. **Laziest thing that works.** No abstraction with one implementation. No dependency
|
|
9
|
+
for what twenty lines do. Standard library first, then an already-installed dep, then
|
|
10
|
+
new code. The shortest working diff wins.
|
|
11
|
+
2. **No unreproducible numbers.** No performance claim enters this repository without a
|
|
12
|
+
script in `assay/` that regenerates it and a committed run. This rule has no
|
|
13
|
+
exceptions, and it applies to READMEs, docs, issues, and commit messages.
|
|
14
|
+
3. **The resolver may be wrong; it may never be silently wrong.** Every decision acri
|
|
15
|
+
makes is recorded in `ledger`. A tool that was considered and skipped is as important
|
|
16
|
+
to log as one that was chosen.
|
|
17
|
+
4. **`gate` is advisory, never authoritative.** A false "no tool needed" makes a model
|
|
18
|
+
answer from memory — confident, fluent, wrong, with no error and no retry. Nothing in
|
|
19
|
+
acri may suppress a tool call the model actually wanted to make.
|
|
20
|
+
5. **Append-only by default.** Anything that rewrites a prompt prefix invalidates the
|
|
21
|
+
provider's cache and costs the user money. If a change rewrites history, it must show
|
|
22
|
+
the arithmetic proving it wins.
|
|
23
|
+
6. **No daemon, no gateway, no port.** acri is an import. A change that requires the user
|
|
24
|
+
to run a process is out of scope by definition.
|
|
25
|
+
7. **Code files stay small** — 250 words, 350 hard cap. Docs are exempt.
|
|
26
|
+
|
|
27
|
+
## What to contribute
|
|
28
|
+
|
|
29
|
+
| Surface | What it looks like |
|
|
30
|
+
|---------|--------------------|
|
|
31
|
+
| **`port` adapters** | A new provider in ~50 lines: take tools + query, return the request payload that provider expects. Highest-value contribution. |
|
|
32
|
+
| **`corpus` ingesters** | Teach acri to read a new tool source (OpenAPI, a registry format, a framework's tool objects). |
|
|
33
|
+
| **`assay` benchmarks** | A reproducible scenario with a committed run. Benchmarks that *disprove* something are more valuable than ones that confirm it. |
|
|
34
|
+
| **`compass` retrieval** | Better ranking. Must come with an `assay` run showing it beats the current default. |
|
|
35
|
+
| **Docs** | Especially corrections. The architecture doc names its own weak points; sharpening them is welcome. |
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
1. Open an issue first for anything non-trivial — a template is provided. Small fixes can
|
|
40
|
+
go straight to a PR.
|
|
41
|
+
2. Fork, branch (`feat/<slug>` or `fix/<slug>`).
|
|
42
|
+
3. Run the self-checks (see `.github/workflows/ci.yml` for what CI runs).
|
|
43
|
+
4. PR describing what it does, **why it is the minimal version**, and what you
|
|
44
|
+
deliberately did not build.
|
|
45
|
+
5. One maintainer review plus green CI merges it.
|
|
46
|
+
|
|
47
|
+
## Commit style
|
|
48
|
+
|
|
49
|
+
Conventional commits (`feat:`, `fix:`, `docs:`, `chore:`). Subjects under 72 characters.
|
|
50
|
+
|
|
51
|
+
## Disagreeing with the design
|
|
52
|
+
|
|
53
|
+
The architecture doc is an argument, not a decree. If you think a layer is wrong, open an
|
|
54
|
+
issue titled `design:` and make the case. Several parts of acri exist specifically because
|
|
55
|
+
an earlier version of the design was wrong and someone said so.
|
|
56
|
+
|
|
57
|
+
## Questions
|
|
58
|
+
|
|
59
|
+
Open a discussion or an issue. Be kind — see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
pyacri-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Piyush Sharma (ScienHAC) and acri contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyacri-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyacri
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A client-side capability resolver: pick the right few tools before the request is sent.
|
|
5
|
+
Project-URL: Homepage, https://github.com/INERATE/acri
|
|
6
|
+
Project-URL: Repository, https://github.com/INERATE/acri
|
|
7
|
+
Project-URL: Issues, https://github.com/INERATE/acri/issues
|
|
8
|
+
Author: Piyush Sharma
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Provides-Extra: assay
|
|
19
|
+
Requires-Dist: google-genai>=1; extra == 'assay'
|
|
20
|
+
Requires-Dist: mcp>=1; extra == 'assay'
|
|
21
|
+
Requires-Dist: openai>=1; extra == 'assay'
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: mcp>=1; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: pyyaml>=6; extra == 'dev'
|
|
26
|
+
Provides-Extra: release
|
|
27
|
+
Requires-Dist: build>=1; extra == 'release'
|
|
28
|
+
Requires-Dist: twine>=5; extra == 'release'
|
|
29
|
+
Provides-Extra: server
|
|
30
|
+
Requires-Dist: mcp>=1; extra == 'server'
|
|
31
|
+
Requires-Dist: pyyaml>=6; extra == 'server'
|
|
32
|
+
Provides-Extra: studio
|
|
33
|
+
Requires-Dist: pyyaml>=6; extra == 'studio'
|
|
34
|
+
Provides-Extra: yaml
|
|
35
|
+
Requires-Dist: pyyaml>=6; extra == 'yaml'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# acri
|
|
39
|
+
|
|
40
|
+
**A**gent **C**apability **R**esolution **I**nterface
|
|
41
|
+
|
|
42
|
+
> DNS resolves a name to an address.
|
|
43
|
+
> **acri resolves an intent to the right tools.**
|
|
44
|
+
|
|
45
|
+
You don't hand a browser all 300 million domains. You shouldn't hand a model all 300 tools.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## The problem
|
|
50
|
+
|
|
51
|
+
Tool selection degrades as your toolset grows. Anthropic's own documentation puts the
|
|
52
|
+
cliff at **30–50 tools**: past that, the model starts picking the wrong one.
|
|
53
|
+
|
|
54
|
+
The fix is to stop sending every schema on every request, and send only the few that
|
|
55
|
+
matter for the current turn. Anthropic ships this as
|
|
56
|
+
[tool search](https://docs.claude.com/en/docs/agents-and-tools/tool-use/tool-search-tool).
|
|
57
|
+
OpenAI ships an equivalent.
|
|
58
|
+
|
|
59
|
+
**Gemini does not. Ollama does not. vLLM does not. Your 8B local model does not.**
|
|
60
|
+
|
|
61
|
+
acri is that layer, for everyone else — one import, no gateway, no daemon, no framework
|
|
62
|
+
to adopt.
|
|
63
|
+
|
|
64
|
+
## What acri is
|
|
65
|
+
|
|
66
|
+
A client-side, provider-agnostic capability resolver. It sits between your code and the
|
|
67
|
+
LLM API, and decides which tools the model gets to see this turn.
|
|
68
|
+
|
|
69
|
+
## What acri is not
|
|
70
|
+
|
|
71
|
+
- **Not an MCP replacement.** MCP defines how tools connect. acri decides which of them
|
|
72
|
+
the model sees. Your existing MCP servers work unchanged.
|
|
73
|
+
- **Not a framework.** No orchestration, no graph, no agent loop. It is a function you
|
|
74
|
+
call. Use it inside LangGraph, inside the raw SDK, or inside nothing.
|
|
75
|
+
- **Not a proxy or gateway.** No process to run, no port to open, no network hop added.
|
|
76
|
+
- **Not a model.** Bring your own key. acri never calls an LLM you didn't ask for.
|
|
77
|
+
|
|
78
|
+
## The system
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
acri
|
|
82
|
+
├── corpus the capability index — MCP servers, OpenAPI, plain functions,
|
|
83
|
+
│ indexed into one searchable body
|
|
84
|
+
├── compass the resolver — intent in, the right k tools out ← the core
|
|
85
|
+
├── router the tier picker — cheap/strong model, chosen once, before generating
|
|
86
|
+
├── port provider adapters — gemini · openai-compatible (OpenAI, vLLM, Ollama, ...)
|
|
87
|
+
├── config acri.yaml — declares capabilities and limits, never control flow
|
|
88
|
+
├── daemon the OpenAI-shaped request handler — acri.run(), nothing more
|
|
89
|
+
├── server `acri up` — binds it: stdlib http.server, SSE, localhost by default
|
|
90
|
+
├── gate the necessity check — does this turn need a tool at all? (advisory)
|
|
91
|
+
├── press the compactor — big payloads to short digests + a handle
|
|
92
|
+
├── sandbox CPU/memory/network/volume limits on stdio MCP servers, via docker
|
|
93
|
+
├── ledger the decision trace — what was chosen, skipped, and what it cost
|
|
94
|
+
├── studio `acri studio` — read-only dashboard over acri.yaml + the ledger, own process/port
|
|
95
|
+
└── assay the proving ground — the only place a benchmark number may come from
|
|
96
|
+
|
|
97
|
+
examples/ end-to-end scripts against a real MCP server — not part of the installed package
|
|
98
|
+
rust/ minimal Rust port of corpus + compass, v0.1 scope only
|
|
99
|
+
typescript/ minimal TypeScript port of corpus + compass, v0.1 scope only
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Status
|
|
103
|
+
|
|
104
|
+
**Pre-alpha — API not yet stable (v0.x, see `docs/decisions.md`). `corpus` + `compass` +
|
|
105
|
+
`port` + `ledger` + `assay` + an exact-match cache + a pre-generation router all exist,
|
|
106
|
+
are tested, and back every claim below with a script or a test file. `daemon` (`acri up`)
|
|
107
|
+
exists too, ahead of its own gate — see the v1.0 roadmap row for what that means.**
|
|
108
|
+
|
|
109
|
+
Not yet published to PyPI — [`.github/workflows/publish.yml`](.github/workflows/publish.yml) exists but
|
|
110
|
+
hasn't been triggered (needs a trusted publisher registered on PyPI's side, then a GitHub Release). The
|
|
111
|
+
distribution name will be `pyacri`, not `acri` — PyPI blocks names within edit-distance-1 of an existing
|
|
112
|
+
package (`acris`, `acr`, and `acre` already exist, all unrelated); `import acri` and the `acri` command
|
|
113
|
+
are unaffected, since PyPI's distribution name and the Python import name are separate settings. Until
|
|
114
|
+
published, install from a clone:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install -e ".[dev]"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import acri
|
|
122
|
+
|
|
123
|
+
tools = acri.from_callables([get_weather, get_stock_price, merge_pull_request])
|
|
124
|
+
corpus = acri.index(tools) # build once, reuse across the whole task
|
|
125
|
+
|
|
126
|
+
result = acri.run("what's the weather in Tokyo?", corpus, my_openai_client)
|
|
127
|
+
print(result.tool_calls) # [{"name": "get_weather", "arguments": '{"city": "Tokyo"}'}]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`acri.run()` resolves, calls the provider, and — if you pass `ledger=acri.Ledger()` — records
|
|
131
|
+
the trace. Prefer to drive the pieces yourself? `acri.resolve(query, corpus, k=5)` returns the
|
|
132
|
+
ranked tools; `acri.gemini` / `acri.openai_compatible` take it from there.
|
|
133
|
+
|
|
134
|
+
Read [`docs/architecture.md`](docs/architecture.md) for the full design, the prior art it
|
|
135
|
+
builds on, and the claims it explicitly refuses to make. Read
|
|
136
|
+
[`docs/decisions.md`](docs/decisions.md) for every capability that was proposed and cut,
|
|
137
|
+
with the evidence that decided it.
|
|
138
|
+
|
|
139
|
+
### First numbers
|
|
140
|
+
|
|
141
|
+
Measured on a synthetic 100-tool corpus spanning 20 domains (github, postgres, stripe,
|
|
142
|
+
aws_ec2, jira, zendesk, ...) with realistic cross-domain confusability, and 50 hand-written
|
|
143
|
+
queries phrased the way someone would actually type them, not as paraphrases of the tool
|
|
144
|
+
descriptions. Reproduce: `pip install -e ".[dev]"` then `python -m assay.recall`. Fixtures
|
|
145
|
+
and script: [`assay/fixtures.json`](assay/fixtures.json), [`assay/recall.py`](assay/recall.py).
|
|
146
|
+
|
|
147
|
+
| k | recall@k | tools shown instead of 100 |
|
|
148
|
+
|---|----------|-----------------------------|
|
|
149
|
+
| 1 | 74% — [`assay/recall.py`](assay/recall.py) | 1 |
|
|
150
|
+
| 3 | 90% — [`assay/recall.py`](assay/recall.py) | 3 |
|
|
151
|
+
| 5 | 100% — [`assay/recall.py`](assay/recall.py) | 5 |
|
|
152
|
+
| 10 | 100% — [`assay/recall.py`](assay/recall.py) | 10 |
|
|
153
|
+
|
|
154
|
+
Both adversarial queries (no correct tool exists in the corpus) correctly resolve to
|
|
155
|
+
nothing, at every k. The k=5 row moved from 86% to 100% ([`assay/recall.py`](assay/recall.py))
|
|
156
|
+
after a small query-side synonym table was added to `compass.py` —
|
|
157
|
+
[`_ALIASES`](acri/compass.py), 10 entries, each traced to
|
|
158
|
+
a real miss, not guessed (`pr` → `pull`, `request`; `rain` → `weather`; `text` → `sms`,
|
|
159
|
+
`message`; and so on). It expands the query only, never the indexed tool text, so a tool's
|
|
160
|
+
own description can't start quietly matching a synonym it never claimed.
|
|
161
|
+
|
|
162
|
+
Recall says the right tool is *available* to the model in a much smaller set — not that
|
|
163
|
+
the model picks it. That's a separate, live-model question:
|
|
164
|
+
[`assay/accuracy.py`](assay/accuracy.py), naive (all 100 tools) vs. acri (top 5), same 50
|
|
165
|
+
queries, real `gemini-2.5-flash` calls, no mocking.
|
|
166
|
+
|
|
167
|
+
**Corrected twice, both times in public.** The first published number here claimed acri roughly doubles accuracy: naive ~24%, acri ~53% (commit history on [`assay/accuracy.py`](assay/accuracy.py)). That was a broken benchmark: every one
|
|
168
|
+
of the 100 fixture tools had an *empty* parameter schema, so a well-behaved model correctly
|
|
169
|
+
declined to fabricate a ticket ID or a SQL string it was never given, and that correct
|
|
170
|
+
caution was scored as a tool-selection failure. Fixing the schemas and the obviously
|
|
171
|
+
under-specified queries produced a second number: naive 72%, acri 74% ([`assay/accuracy.py`](assay/accuracy.py)), that this README called modest and honest. It
|
|
172
|
+
was honest, but still not fully clean: a live diagnostic pass
|
|
173
|
+
(`assay/diagnose.py`) surfaced 9 more queries that satisfied their tool's *declared*
|
|
174
|
+
`required` fields but still left a genuinely necessary value unstated — "bump it up a size"
|
|
175
|
+
with no target size, "point \[domain\] at the new server" with no address, an opportunity
|
|
176
|
+
referenced by company name instead of ID. Full list and reasoning:
|
|
177
|
+
[`docs/decisions.md`](docs/decisions.md). Fixed the same way as the first round — supply the
|
|
178
|
+
missing value, or mark the field `required` if the action is meaningless without it — never
|
|
179
|
+
by rewording toward the answer. Third run, current:
|
|
180
|
+
|
|
181
|
+
| Arm | accuracy | median latency |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| naive (100 tools) | 84% — [`assay/accuracy.py`](assay/accuracy.py) | 1792 ms |
|
|
184
|
+
| acri (top 5) | 92% — [`assay/accuracy.py`](assay/accuracy.py) | 1596 ms |
|
|
185
|
+
|
|
186
|
+
**An 8-point gap this time, not noise** — for scale, naive's own score moved 2 points
|
|
187
|
+
between the previous two runs with zero code changes on its side, so 2 points is this
|
|
188
|
+
benchmark's rough noise floor at n=50; 8 is well outside it. `assay/diagnose.py` confirms
|
|
189
|
+
`resolver_miss=0` — every remaining acri miss is the model's, not compass's — and all four
|
|
190
|
+
share one shape: the model declines to act (`picked: None`, not a wrong tool) on a
|
|
191
|
+
write/mutating request — refund a charge, launch a server, send an email, book a meeting —
|
|
192
|
+
even once every *required* field is present. That reads as the model being conservative
|
|
193
|
+
about consequential actions specifically, not a resolver or benchmark defect, and it is left
|
|
194
|
+
unpatched on purpose: supplying more certainty than a real caller would have crosses from
|
|
195
|
+
fixing the benchmark into fixing the test until it passes. This is still a single run at each
|
|
196
|
+
stage; repeat runs would firm up the interval and are a natural next step, not yet taken.
|
|
197
|
+
Reproduce: `GEMINI_API_KEY=... python -m assay.accuracy --provider gemini`, or via
|
|
198
|
+
[Vertex AI / ADC](assay/clients.py):
|
|
199
|
+
`GOOGLE_APPLICATION_CREDENTIALS=... GOOGLE_CLOUD_PROJECT=... python -m assay.accuracy --provider vertex`.
|
|
200
|
+
|
|
201
|
+
What this means for the project's claims: **recall — the context-window reduction — remains
|
|
202
|
+
the number that survived every round unaffected** (BM25 never reads a tool's parameters, so
|
|
203
|
+
none of the query-completeness fixes could have moved it; only the synonym layer did, on
|
|
204
|
+
purpose). The accuracy claim is now both real and no longer small, but it took three
|
|
205
|
+
corrected rounds to get a benchmark that measures tool selection instead of measuring
|
|
206
|
+
whether the fixtures gave the model enough to work with — a fact worth remembering before
|
|
207
|
+
trusting the next number this project publishes, including from this project's own authors.
|
|
208
|
+
|
|
209
|
+
**Also verified against a real MCP server, not a mock:**
|
|
210
|
+
[`assay/mcp_live.py`](assay/mcp_live.py) spawns the official
|
|
211
|
+
`@modelcontextprotocol/server-filesystem` over stdio, ingests its live `tools/list` response
|
|
212
|
+
through `acri.adapters.from_mcp_tools()`, resolves a query against the real 14-tool corpus,
|
|
213
|
+
and executes the top-ranked tool through an actual `tools/call`. `list_directory` was the
|
|
214
|
+
top match for "what files are in this directory" (score 1.000 of 14 candidates), and its
|
|
215
|
+
live result contained a marker file planted before the run specifically to prove the pipeline
|
|
216
|
+
is real. Run: `python -m assay.mcp_live <a-directory> "<query>"` (needs Node.js on `PATH`).
|
|
217
|
+
|
|
218
|
+
`compass.resolve()` itself, over 1,040 calls against the same 100-tool corpus: p50 0.040ms,
|
|
219
|
+
p95 0.087ms ([`assay/latency.py`](assay/latency.py)). Not headlined on purpose — see
|
|
220
|
+
[`docs/decisions.md`](docs/decisions.md) for why resolver latency is beside the point
|
|
221
|
+
against a network call measured in hundreds of milliseconds.
|
|
222
|
+
|
|
223
|
+
### Does it hold up at scale?
|
|
224
|
+
|
|
225
|
+
[`assay/scale.py`](assay/scale.py) reruns recall@k and latency against
|
|
226
|
+
[`assay/fixtures_500.json`](assay/fixtures_500.json) — the same 100 tools and 52 gold queries
|
|
227
|
+
above, plus ~400 more tools across ~40 new domains (gitlab and bitbucket alongside github,
|
|
228
|
+
discord and telegram alongside slack, and so on), so corpus size is the only thing that
|
|
229
|
+
changed. It isn't: recall@5 drops from 100% to 92%, recall@1 from 74% to 60% — [`assay/scale.py`](assay/scale.py).
|
|
230
|
+
Latency stays fast — p50 0.179ms, p95 0.285ms at 504 tools, still nothing against a network
|
|
231
|
+
call. The context-window case gets stronger, not weaker, at this scale: 5 of 504 tools shown is a 99% reduction — [`assay/scale.py`](assay/scale.py), the same
|
|
232
|
+
arithmetic the 100-tool table above already uses. Recall genuinely degrades, though, and
|
|
233
|
+
that's the metric that matters more than either latency or the reduction percentage.
|
|
234
|
+
|
|
235
|
+
The honest reason, checked query by query rather than assumed: BM25 has no semantic
|
|
236
|
+
understanding, so once a real lexical competitor exists it can win. "Is PR 42 on the
|
|
237
|
+
acme/webapp repo merged" now out-scores `github_get_pull_request` with `bitbucket`'s own
|
|
238
|
+
pull-request tools, because neither tool's description names its own platform and the query
|
|
239
|
+
doesn't say which host it means — a gap the original 100-tool corpus never had to close, since
|
|
240
|
+
no second git host existed to be confused with. Not patched: rewording the query toward the
|
|
241
|
+
answer or adding an alias here would be tuning the benchmark, not fixing the resolver — see
|
|
242
|
+
`docs/decisions.md`'s "Corrected twice, both times in public" for why that line gets held.
|
|
243
|
+
Reproduce: `python -m assay.scale`.
|
|
244
|
+
|
|
245
|
+
### Diagrams
|
|
246
|
+
|
|
247
|
+

|
|
248
|
+
|
|
249
|
+

|
|
250
|
+
|
|
251
|
+
Both are static SVGs generated from the numbers above, nothing more — no simulator, no live
|
|
252
|
+
trace, no feature these diagrams show that isn't already shipped and linked to the script
|
|
253
|
+
that measured it. If a future version adds `studio` (the real trace visualizer — see
|
|
254
|
+
[`docs/decisions.md`](docs/decisions.md)), it replaces these; until then, these are it.
|
|
255
|
+
|
|
256
|
+
### Roadmap
|
|
257
|
+
|
|
258
|
+
| Version | Scope | Gate to ship |
|
|
259
|
+
|---------|-------|--------------|
|
|
260
|
+
| **v0.1** | `corpus` + `compass` + `port` + minimal `ledger` | **Shipped.** `pytest` green, no native deps. |
|
|
261
|
+
| **v0.2** | `assay` | **Shipped.** Recall, latency, and a live accuracy result, all above — the accuracy number was corrected twice after the benchmark itself was found to be flawed, both times in public. |
|
|
262
|
+
| **v0.3** | Pre-generation router, exact-match cache | **Shipped.** Cache: `acri.run(..., cache={})` skips a repeated (provider, model, query, offered tools) call — [`acri/port.py`](acri/port.py)'s `cached_call`. Router: `acri.run(..., cheap_model=...)` routes one call to a cheaper tier, once, before generating — [`acri/router.py`](acri/router.py). Eligibility (is this call stateless and prefix-free?) is the caller's judgment, not acri's — see `docs/architecture.md` §4.4. Tests: [`tests/test_ports.py`](tests/test_ports.py), [`tests/test_router.py`](tests/test_router.py), [`tests/test_integration.py`](tests/test_integration.py). |
|
|
263
|
+
| **later** | `gate`, `press` | **Shipped, ahead of their gate** ("only if `ledger` data proves they are needed" — no real ledger data exists yet; built at the maintainer's explicit request, same override pattern as the daemon). `gate` ([`acri/gate.py`](acri/gate.py)): a threshold on the raw BM25 score `compass` already computes — `Resolved.score` is always 1.0 for the winner by construction, so `raw_top_score()` exposes the un-normalized number instead. No default threshold ships; picking one needs the same ledger data the gate condition itself was waiting for, so it stays caller-supplied. `press` ([`acri/press.py`](acri/press.py)): large tool results become a digest plus a handle to the untruncated original — `recover()` gets it back, which is the answer to the "digest drops an identifier" risk `docs/architecture.md` names. TOON-style header-once encoding for uniform tabular results; not the `toon-format` PyPI package, whose `encode()` raises `NotImplementedError` as of 0.1.0 — checked directly before writing this instead. Tests: [`tests/test_gate.py`](tests/test_gate.py), [`tests/test_press.py`](tests/test_press.py). |
|
|
264
|
+
| **v1.1** | `sandbox` — CPU/memory/network/volume limits on stdio MCP servers; `find_more_tools`, the escape hatch `docs/architecture.md` §4.1 always documented | **Shipped.** [`acri/sandbox.py`](acri/sandbox.py) wraps an `mcp:` entry's command in `docker run -i --rm` with resource limits and, via `acri.yaml`'s `sandbox.volumes:` (host path → container path), `-v` mounts — so a sandboxed filesystem/git server can see a real project folder; append `:ro` to a container path yourself for read-only, no separate flag. Calls the container engine, never reimplements namespaces/cgroups, per `docs/decisions.md`. Docker Desktop's daemon wasn't running in the environment this was built in, so the command-construction is tested ([`tests/test_sandbox.py`](tests/test_sandbox.py)) but an actual `docker run` proxying a real MCP session is not — stated plainly, not glossed over. `find_more_tools` ([`acri/escape_hatch.py`](acri/escape_hatch.py)): `acri.run()` always appends it to the tools actually sent to the provider — not to `compass.resolve()`'s own output, so the recall@k numbers above are unaffected, and not to the ledger's `offered`, which stays the real resolution candidates only ([`tests/test_integration.py`](tests/test_integration.py)). Calling it re-searches the full corpus; the caller executes it and appends the result like any other tool call, per §4.1's "append, never rewrite" rule — acri does not auto-execute it. Tests: [`tests/test_sandbox.py`](tests/test_sandbox.py), [`tests/test_config.py`](tests/test_config.py), [`tests/test_escape_hatch.py`](tests/test_escape_hatch.py). Live, end-to-end demo against a real MCP server: [`examples/live_demo.py`](examples/live_demo.py). |
|
|
265
|
+
| **v1.0** | `daemon` — long-lived process, OpenAI-compatible HTTP endpoint | **Shipped, ahead of its own gate.** `docs/decisions.md`: "the daemon is built after the library has users who want it, not before" — not met (no PyPI release yet), and built anyway at the maintainer's explicit request; that override is deliberate, not an oversight. `acri up` ([`acri/server.py`](acri/server.py), stdlib `http.server` only) connects to `acri.yaml`'s `mcp:` entries once at startup ([`acri/mcp_connect.py`](acri/mcp_connect.py)), then serves `/v1/chat/completions` over SSE via [`acri/daemon.py`](acri/daemon.py)'s handler — the same `acri.run()` the library calls, not a reimplementation. Binds `127.0.0.1` by default; conversation content is opt-in (`--log-conversations`), off by default via `RedactingLedger`. Verified against a real MCP server and a real Gemini call, not just fakes — which surfaced and fixed a real bug: Gemini's function-calling schema rejects the `$schema` key real MCP servers commonly add, invisible to every synthetic fixture in this repo (`acri/schemas.py`). Tests: [`tests/test_config.py`](tests/test_config.py), [`tests/test_cli.py`](tests/test_cli.py), [`tests/test_daemon.py`](tests/test_daemon.py), [`tests/test_server.py`](tests/test_server.py), [`tests/test_schemas.py`](tests/test_schemas.py). Wire-level SSE only — one blocking `acri.run()` call chunked out, not a true streaming upstream call. |
|
|
266
|
+
| **v1.2** | `studio`, minimal Rust and TypeScript ports, PyPI publish workflow | **Shipped, ahead of gate, each stated plainly.** `studio` ([`acri/studio.py`](acri/studio.py)): decisions.md already had a full design for this (`docs/decisions.md`, "`studio` — the trace visualizer") — built to that spec, not invented from scratch; two honest simplifications against it recorded in [`docs/architecture.md`](docs/architecture.md) §4.5. Own process, own port (`acri studio`, default 8099), reads only `acri.yaml` and `.acri/ledger.jsonl`, never connects to an MCP server or a model — a separate `pip install pyacri[studio]` extra, per decisions.md's own naming (PyPI's distribution name; `import acri` and the `acri` command are unaffected). Two views: the static mesh (servers/models/tools ever seen) and the live trace (recent ledger entries, polled every 2s). `rust/` and `typescript/`: v0.1-scope-only ports of `corpus` + `compass` (BM25 resolve()) — nothing else. Rust's gate ("only if `assay` shows Python ranking is a measurable share of a turn") has been measured and answered no (`assay/scale.py`: p50 0.179ms against network calls running hundreds of ms+); TypeScript's gate ("after the Python package has users, never in parallel") is unmet on its own terms, no PyPI release exists yet. Both verified against `tests/test_compass.py`'s exact cases: Rust 9/9 (`cargo test`), TypeScript 5/5 (`npm test`). [`.github/workflows/publish.yml`](.github/workflows/publish.yml): OIDC trusted publishing, triggered only by a published GitHub Release, not a tag push — needs a one-time trusted-publisher registration on PyPI's side before it can run at all. Tests: [`tests/test_studio.py`](tests/test_studio.py), [`tests/test_studio_data.py`](tests/test_studio_data.py). |
|
|
267
|
+
|
|
268
|
+
## The claims policy
|
|
269
|
+
|
|
270
|
+
This project makes no performance claim it cannot reproduce.
|
|
271
|
+
|
|
272
|
+
No number appears in this repository — README, docs, paper, or commit message — unless a
|
|
273
|
+
script in `assay/` regenerates it from a public benchmark, and the run is committed
|
|
274
|
+
alongside it. Estimates are labelled as estimates. Projections are labelled as
|
|
275
|
+
projections.
|
|
276
|
+
|
|
277
|
+
If you find a number here without its receipt, that is a bug. Please
|
|
278
|
+
[open an issue](../../issues/new).
|
|
279
|
+
|
|
280
|
+
## Prior art
|
|
281
|
+
|
|
282
|
+
acri stands on published work and does not pretend otherwise:
|
|
283
|
+
|
|
284
|
+
- [RAG-MCP](https://arxiv.org/abs/2505.03275) — retrieval over tool schemas to cut prompt bloat
|
|
285
|
+
- [When2Call](https://arxiv.org/abs/2504.18851) — when (not) to call tools
|
|
286
|
+
- [Anthropic tool search](https://docs.claude.com/en/docs/agents-and-tools/tool-use/tool-search-tool) and [context editing](https://docs.claude.com/en/docs/build-with-claude/context-editing) — the same idea, shipped provider-side
|
|
287
|
+
|
|
288
|
+
acri's contribution is **placement**: the same capability, client-side and
|
|
289
|
+
provider-agnostic, for the models that don't have it natively.
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT — see [LICENSE](LICENSE).
|
|
294
|
+
|
|
295
|
+
Built by [Piyush Sharma](https://github.com/ScienHAC) under [INERATE](https://github.com/INERATE),
|
|
296
|
+
alongside [Atelier](https://github.com/INERATE/atelier).
|