convene 0.1.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.
- convene-0.1.0/.github/ISSUE_TEMPLATE/measurement.yml +65 -0
- convene-0.1.0/.github/ISSUE_TEMPLATE/tool-problem.yml +41 -0
- convene-0.1.0/.github/workflows/ci.yml +46 -0
- convene-0.1.0/.github/workflows/release.yml +55 -0
- convene-0.1.0/.gitignore +32 -0
- convene-0.1.0/CONTRIBUTING.md +136 -0
- convene-0.1.0/FINDINGS.md +239 -0
- convene-0.1.0/LICENSE +21 -0
- convene-0.1.0/PKG-INFO +389 -0
- convene-0.1.0/README.md +364 -0
- convene-0.1.0/ROADMAP.md +118 -0
- convene-0.1.0/claude_cli.py +137 -0
- convene-0.1.0/examples/experts.toml +102 -0
- convene-0.1.0/pyproject.toml +59 -0
- convene-0.1.0/src/convene/__init__.py +184 -0
- convene-0.1.0/src/convene/__main__.py +8 -0
- convene-0.1.0/src/convene/auth.py +239 -0
- convene-0.1.0/src/convene/cli.py +581 -0
- convene-0.1.0/src/convene/config.py +112 -0
- convene-0.1.0/src/convene/doctor.py +283 -0
- convene-0.1.0/src/convene/errors.py +43 -0
- convene-0.1.0/src/convene/experts.py +360 -0
- convene-0.1.0/src/convene/logging_.py +55 -0
- convene-0.1.0/src/convene/py.typed +0 -0
- convene-0.1.0/src/convene/runtime.py +352 -0
- convene-0.1.0/src/convene/sessions.py +637 -0
- convene-0.1.0/tests/conftest.py +20 -0
- convene-0.1.0/tests/test_auth.py +82 -0
- convene-0.1.0/tests/test_experts.py +133 -0
- convene-0.1.0/tests/test_live.py +83 -0
- convene-0.1.0/tests/test_runtime.py +161 -0
- convene-0.1.0/tests/test_sessions.py +125 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Measurement
|
|
2
|
+
description: Report a number — a confirmation, a contradiction, or a new result
|
|
3
|
+
labels: ["measurement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Measurements are the most valuable thing you can contribute here.
|
|
9
|
+
A run that **contradicts** a table in FINDINGS.md is worth more than
|
|
10
|
+
another confirmation of it.
|
|
11
|
+
|
|
12
|
+
Every claim needs its sample size and the build it came from. See
|
|
13
|
+
[CONTRIBUTING.md](../blob/develop/CONTRIBUTING.md#the-claim-standard).
|
|
14
|
+
- type: input
|
|
15
|
+
id: claude-version
|
|
16
|
+
attributes:
|
|
17
|
+
label: Claude Code version
|
|
18
|
+
description: "`claude --version`"
|
|
19
|
+
placeholder: "2.1.237 (Claude Code)"
|
|
20
|
+
validations: { required: true }
|
|
21
|
+
- type: input
|
|
22
|
+
id: platform
|
|
23
|
+
attributes:
|
|
24
|
+
label: OS and architecture
|
|
25
|
+
placeholder: "macOS 15.3 arm64 / Ubuntu 24.04 x86_64 / Windows 11"
|
|
26
|
+
validations: { required: true }
|
|
27
|
+
- type: dropdown
|
|
28
|
+
id: auth
|
|
29
|
+
attributes:
|
|
30
|
+
label: Auth mode
|
|
31
|
+
options: ["login (subscription)", "sandbox_token (subscription)", "api_key"]
|
|
32
|
+
validations: { required: true }
|
|
33
|
+
- type: dropdown
|
|
34
|
+
id: topic
|
|
35
|
+
attributes:
|
|
36
|
+
label: Which claim does this touch?
|
|
37
|
+
options:
|
|
38
|
+
- "1. -p uses the subscription login / --bare does not"
|
|
39
|
+
- "2. Prompt cache — the ~9.5x, or where the threshold sits"
|
|
40
|
+
- "3. Concurrency ceiling"
|
|
41
|
+
- "4. Multi-turn cost and behaviour"
|
|
42
|
+
- "5. Auth failure returning success"
|
|
43
|
+
- "6. Lockdown flags worth ~88x"
|
|
44
|
+
- "A question FINDINGS.md lists as unmeasured"
|
|
45
|
+
- "Something not covered yet"
|
|
46
|
+
validations: { required: true }
|
|
47
|
+
- type: textarea
|
|
48
|
+
id: numbers
|
|
49
|
+
attributes:
|
|
50
|
+
label: The numbers
|
|
51
|
+
description: >
|
|
52
|
+
Token counts and costs, not just costs — a cost claim without tokens
|
|
53
|
+
cannot be checked. Say how many runs (n) each row represents.
|
|
54
|
+
render: text
|
|
55
|
+
placeholder: |
|
|
56
|
+
n=3 bursts per row, claude-sonnet-5, effort=low
|
|
57
|
+
n=20 wall=31.4s errors=7 (5x rate_limit, 2x overloaded)
|
|
58
|
+
validations: { required: true }
|
|
59
|
+
- type: textarea
|
|
60
|
+
id: command
|
|
61
|
+
attributes:
|
|
62
|
+
label: How to reproduce it
|
|
63
|
+
description: The exact command, ideally `convene bench ...`
|
|
64
|
+
render: shell
|
|
65
|
+
validations: { required: true }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Tool problem
|
|
2
|
+
description: convene itself is broken, or does something it shouldn't
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
**If a call started billing API credits instead of your subscription,
|
|
9
|
+
say so in the title** — that is the most serious class of bug here.
|
|
10
|
+
|
|
11
|
+
Please run `convene doctor` first and paste the output; it answers
|
|
12
|
+
most of what would otherwise be a round trip.
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: doctor
|
|
15
|
+
attributes:
|
|
16
|
+
label: "`convene doctor` output"
|
|
17
|
+
description: Redact the email if you like — the plan and auth method are what matter.
|
|
18
|
+
render: text
|
|
19
|
+
validations: { required: true }
|
|
20
|
+
- type: input
|
|
21
|
+
id: version
|
|
22
|
+
attributes:
|
|
23
|
+
label: convene and Python versions
|
|
24
|
+
placeholder: "convene 0.1.0, Python 3.12.4"
|
|
25
|
+
validations: { required: true }
|
|
26
|
+
- type: textarea
|
|
27
|
+
id: what-happened
|
|
28
|
+
attributes:
|
|
29
|
+
label: What happened, and what you expected
|
|
30
|
+
validations: { required: true }
|
|
31
|
+
- type: textarea
|
|
32
|
+
id: repro
|
|
33
|
+
attributes:
|
|
34
|
+
label: Smallest reproduction
|
|
35
|
+
render: python
|
|
36
|
+
validations: { required: true }
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: traceback
|
|
39
|
+
attributes:
|
|
40
|
+
label: Traceback or error output
|
|
41
|
+
render: text
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: pip install -e ".[dev]"
|
|
22
|
+
# The `live` marker is excluded by default in pyproject. CI has no Claude
|
|
23
|
+
# Code login, and tests that spend money must never run unattended.
|
|
24
|
+
- run: pytest -q
|
|
25
|
+
- run: ruff check src tests claude_cli.py
|
|
26
|
+
- run: ruff format --check src tests claude_cli.py
|
|
27
|
+
|
|
28
|
+
scope-guard:
|
|
29
|
+
# The scope rule in the README is load-bearing, so it is enforced rather
|
|
30
|
+
# than merely documented: no HTTP server, no OpenAI-compatible endpoint,
|
|
31
|
+
# and never the --bare flag.
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- name: No --bare flag anywhere in the package
|
|
36
|
+
run: |
|
|
37
|
+
if grep -rn -- '"--bare"' src/; then
|
|
38
|
+
echo "::error::--bare forces API-key auth and never reads OAuth."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
- name: No network server in the package
|
|
42
|
+
run: |
|
|
43
|
+
if grep -rnE '^\s*(import|from)\s+(http\.server|socketserver|flask|fastapi|uvicorn|aiohttp)\b' src/; then
|
|
44
|
+
echo "::error::convene exposes no network endpoint. See README > Scope."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a version tag is pushed.
|
|
4
|
+
#
|
|
5
|
+
# Uses PyPI Trusted Publishing, so there is no API token in this repo and
|
|
6
|
+
# nothing to leak or rotate. One-time setup on PyPI:
|
|
7
|
+
#
|
|
8
|
+
# pypi.org -> Your projects -> Publishing -> Add a new pending publisher
|
|
9
|
+
# PyPI project name : convene
|
|
10
|
+
# Owner : YashShelar007
|
|
11
|
+
# Repository : convene
|
|
12
|
+
# Workflow name : release.yml
|
|
13
|
+
# Environment : pypi
|
|
14
|
+
#
|
|
15
|
+
# Then: git tag v0.1.0 && git push origin v0.1.0
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
tags: ["v*"]
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
- run: pip install build
|
|
30
|
+
- run: python -m build
|
|
31
|
+
- name: Tag must match the version in pyproject
|
|
32
|
+
run: |
|
|
33
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
34
|
+
pkg=$(ls dist/*.tar.gz | sed -E 's/.*convene-(.*)\.tar\.gz/\1/')
|
|
35
|
+
if [ "$tag" != "$pkg" ]; then
|
|
36
|
+
echo "::error::tag $tag does not match built version $pkg"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
- uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
needs: build
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
environment: pypi
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write # required for Trusted Publishing
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
convene-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Credentials and machine identity. Never commit these.
|
|
2
|
+
data/
|
|
3
|
+
*.oauth-token
|
|
4
|
+
oauth-token
|
|
5
|
+
.credentials.json
|
|
6
|
+
|
|
7
|
+
# Call logs and ledgers contain your prompts, costs and account activity.
|
|
8
|
+
logs/
|
|
9
|
+
*.log
|
|
10
|
+
*.sqlite3
|
|
11
|
+
*.db
|
|
12
|
+
|
|
13
|
+
# Local expert registries may hold proprietary prompts.
|
|
14
|
+
experts.local.toml
|
|
15
|
+
convene.local.toml
|
|
16
|
+
|
|
17
|
+
# Python
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*.egg-info/
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
|
|
29
|
+
# macOS
|
|
30
|
+
.DS_Store
|
|
31
|
+
|
|
32
|
+
scratch/
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This repo's product is **verified claims about an undocumented interface**, and
|
|
4
|
+
a small library that acts on them. The code is thin; the value is in knowing
|
|
5
|
+
which assertions are backed by how much evidence. That shapes everything below.
|
|
6
|
+
|
|
7
|
+
## Branches
|
|
8
|
+
|
|
9
|
+
| Branch | What it means |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `main` | Claims that have been verified, and code that has been run. Treat it as published. |
|
|
12
|
+
| `develop` | Integration. New measurements and features land here first. |
|
|
13
|
+
|
|
14
|
+
- `develop` is the **default branch**. Open PRs against it.
|
|
15
|
+
- `main` changes only by PR from `develop`, tagged on merge.
|
|
16
|
+
- Maintainers push to `develop` directly for doc fixes and verified findings.
|
|
17
|
+
Outside contributions come as PRs.
|
|
18
|
+
|
|
19
|
+
Releases are tagged when a **claim set** changes — a new measurement, a
|
|
20
|
+
retraction, a corrected derivation — or when the API changes. The version
|
|
21
|
+
people cite should match what they read.
|
|
22
|
+
|
|
23
|
+
## The claim standard
|
|
24
|
+
|
|
25
|
+
The most useful contribution here is usually a measurement, not a patch. It is
|
|
26
|
+
also the easiest thing to get wrong, because a single lucky run looks exactly
|
|
27
|
+
like a law.
|
|
28
|
+
|
|
29
|
+
**Every empirical claim carries its sample size and the build it came from.**
|
|
30
|
+
|
|
31
|
+
- Good: "cache engages between 914 and 1232 total prompt tokens
|
|
32
|
+
(claude-sonnet-5, Claude Code 2.1.237, macOS arm64, n=1 per size across 8
|
|
33
|
+
sizes)"
|
|
34
|
+
- Not good: "the cache threshold is 1024 tokens"
|
|
35
|
+
|
|
36
|
+
If you can't reach n>1, say so. "Observed once" is a fine claim.
|
|
37
|
+
"Anthropic changed X" is not, unless you have both sides of the change.
|
|
38
|
+
|
|
39
|
+
Claims about *cost* must report the tokens too. `total_cost_usd` is a
|
|
40
|
+
client-side list-price estimate, not a bill, and on subscription auth it is an
|
|
41
|
+
accounting figure rather than a charge. A cost claim without the token counts
|
|
42
|
+
cannot be checked.
|
|
43
|
+
|
|
44
|
+
## What's most wanted
|
|
45
|
+
|
|
46
|
+
The open questions are listed at the bottom of [FINDINGS.md](FINDINGS.md). The
|
|
47
|
+
highest-value ones:
|
|
48
|
+
|
|
49
|
+
1. **The real concurrency ceiling.** n=20 is simply the largest burst anyone
|
|
50
|
+
has tried. Where does it actually start erroring, and what does the envelope
|
|
51
|
+
look like when it does?
|
|
52
|
+
2. **A Linux or Windows column.** Every number in FINDINGS.md is macOS arm64.
|
|
53
|
+
3. **Cache TTL.** Warm reads were ~30s apart. How long does an entry live?
|
|
54
|
+
4. **A build newer than 2.1.237** — especially one where `--bare` has become
|
|
55
|
+
the default for `-p`, which would break subscription auth entirely.
|
|
56
|
+
5. **Counter-examples.** A run that contradicts a table in FINDINGS.md is worth
|
|
57
|
+
more than another confirmation of it.
|
|
58
|
+
|
|
59
|
+
Reproduce the existing numbers first:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
convene bench
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Scope: what will be declined
|
|
66
|
+
|
|
67
|
+
convene runs the first-party `claude` binary locally, under the user's own
|
|
68
|
+
login. It is not a proxy. Three properties are load-bearing, and PRs that
|
|
69
|
+
change any of them will be declined regardless of quality:
|
|
70
|
+
|
|
71
|
+
- **No network endpoint.** No HTTP server, no OpenAI-compatible shim, no
|
|
72
|
+
daemon that other machines or programs can call. See *Scope* in the README
|
|
73
|
+
for why — this is the exact shape Anthropic blocked in 2026.
|
|
74
|
+
- **No credential extraction or forwarding.** The library reads no token files
|
|
75
|
+
it did not create, and sends credentials nowhere.
|
|
76
|
+
- **No silent API-key fallback.** `AuthMode.API_KEY` must never be selected
|
|
77
|
+
implicitly, and the key-stripping in `subprocess_env` must not be weakened.
|
|
78
|
+
|
|
79
|
+
Feature ideas inside scope are in [ROADMAP.md](ROADMAP.md).
|
|
80
|
+
|
|
81
|
+
## Hard rules for the code
|
|
82
|
+
|
|
83
|
+
Each corresponds to a way this has already gone wrong, or would.
|
|
84
|
+
|
|
85
|
+
- **Never pass `--bare`.** It forces API-key auth and never reads OAuth. It
|
|
86
|
+
would silently move billing from a subscription to API credits.
|
|
87
|
+
- **Never weaken the `is_error` check.** Auth failure arrives as
|
|
88
|
+
`subtype: "success"` with exit code 0. `subtype == "success"` alone does not
|
|
89
|
+
catch it. Pinned by `tests/test_runtime.py`.
|
|
90
|
+
- **Never drop a lockdown flag** without a measurement showing the cost is
|
|
91
|
+
unchanged. They are worth ~88x together.
|
|
92
|
+
- **Never strip `--session-id`/`--resume` calls of session persistence.**
|
|
93
|
+
`--no-session-persistence` makes a session unresumable, which defeats the
|
|
94
|
+
point of a durable session.
|
|
95
|
+
- **Keep the core dependency-free.** A tool for reducing spend should not drag
|
|
96
|
+
in a tree of transitive dependencies. `tomllib` is why the floor is 3.11.
|
|
97
|
+
- **Normalise costs, don't pass them through.** Live sessions report cumulative
|
|
98
|
+
cost, durable ones report per-call. `Turn.cost_usd` is always incremental.
|
|
99
|
+
|
|
100
|
+
## Releasing
|
|
101
|
+
|
|
102
|
+
Releases go out through PyPI Trusted Publishing — there is no API token in this
|
|
103
|
+
repo. The one-time PyPI setup is documented at the top of
|
|
104
|
+
[`.github/workflows/release.yml`](.github/workflows/release.yml).
|
|
105
|
+
|
|
106
|
+
After that, a release is:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# on develop, with FINDINGS.md and the version in pyproject.toml both current
|
|
110
|
+
gh pr create --base main --head develop
|
|
111
|
+
# once merged:
|
|
112
|
+
git checkout main && git pull
|
|
113
|
+
git tag v0.1.1 && git push origin v0.1.1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The workflow refuses to publish if the tag does not match the version in
|
|
117
|
+
`pyproject.toml`, so bump that in the same PR.
|
|
118
|
+
|
|
119
|
+
## Testing a change
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip install -e ".[dev]"
|
|
123
|
+
pytest # 46 tests, no spend, no network
|
|
124
|
+
ruff check src tests
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Tests that make real calls are marked `live` and excluded by default. Run them
|
|
128
|
+
before anything touching the runtime, sessions, or auth:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pytest -m live # a few cents, needs a working `claude` login
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If you change a documented number, change FINDINGS.md in the same PR and say in
|
|
135
|
+
the description which command you actually ran. A number in the docs that no
|
|
136
|
+
test or bench command reproduces is a bug.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Findings
|
|
2
|
+
|
|
3
|
+
Measured behaviour of `claude -p` used as an inference endpoint. Everything
|
|
4
|
+
here carries its sample size and the build it came from, because this document
|
|
5
|
+
is the product — the code is a thin wrapper around these facts.
|
|
6
|
+
|
|
7
|
+
Reproduce any of it with:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
convene bench
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Environment for every measurement below unless stated otherwise:** Claude
|
|
14
|
+
Code **2.1.237**, macOS 15 (arm64), subscription auth (`authMethod:
|
|
15
|
+
claude.ai`, Max plan), model `claude-sonnet-5`, `effort=low`, calls locked down
|
|
16
|
+
with the flag set in [`runtime.py`](src/convene/runtime.py).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. `claude -p` uses your subscription login. `--bare` does not.
|
|
21
|
+
|
|
22
|
+
This is the most widely repeated wrong claim about headless Claude Code, and
|
|
23
|
+
it appears in blog posts and search summaries as *"the `-p` flag bypasses OAuth
|
|
24
|
+
and requires an `ANTHROPIC_API_KEY`."*
|
|
25
|
+
|
|
26
|
+
It is false, and Anthropic's own documentation says which half is true. From
|
|
27
|
+
[the headless docs](https://code.claude.com/docs/en/headless), on `--bare`:
|
|
28
|
+
|
|
29
|
+
> Set `ANTHROPIC_API_KEY` before running it, because bare mode doesn't use your
|
|
30
|
+
> subscription login.
|
|
31
|
+
|
|
32
|
+
> In bare mode, Claude Code never reads OAuth credentials or the system keychain.
|
|
33
|
+
|
|
34
|
+
So the API-key requirement belongs to `--bare`, not to `-p`. A plain `-p` run
|
|
35
|
+
reads your OAuth login normally. Every call in this document was made with no
|
|
36
|
+
API key in the environment (n≈150 calls, all succeeded).
|
|
37
|
+
|
|
38
|
+
**This is why convene never passes `--bare`,** despite it being faster to
|
|
39
|
+
start and despite Anthropic recommending it for scripted calls.
|
|
40
|
+
|
|
41
|
+
### The forward risk
|
|
42
|
+
|
|
43
|
+
The same page says:
|
|
44
|
+
|
|
45
|
+
> `--bare` is the recommended mode for scripted and SDK calls, and will become
|
|
46
|
+
> the default for `-p` in a future release.
|
|
47
|
+
|
|
48
|
+
If that lands and there is no opt-out, subscription auth through this library
|
|
49
|
+
breaks. `convene doctor` probes for it with a real call rather than comparing
|
|
50
|
+
version strings, because the failure will not announce itself.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 2. The prompt cache works, and it is worth ~9.5x
|
|
55
|
+
|
|
56
|
+
Same ~4.4k-token system prompt, three **separate processes**, no session
|
|
57
|
+
resumption, ~30s apart (n=1 per row):
|
|
58
|
+
|
|
59
|
+
| | `cache_creation` | `cache_read` | `input_tokens` | cost | elapsed |
|
|
60
|
+
|---|---:|---:|---:|---:|---:|
|
|
61
|
+
| cold | 4434 | 0 | 2 | **$0.02670** | 7.1s |
|
|
62
|
+
| warm | 243 | 4191 | 2 | **$0.00281** | 6.5s |
|
|
63
|
+
| warm | 243 | 4191 | 2 | **$0.00281** | 6.5s |
|
|
64
|
+
|
|
65
|
+
**9.5x cheaper**, and the cache survives process death because it lives
|
|
66
|
+
server-side keyed on the prompt prefix.
|
|
67
|
+
|
|
68
|
+
The practical consequence inverts ordinary prompt advice: a long, *stable*
|
|
69
|
+
system prompt is the cheap option, and a short one you retune per call is the
|
|
70
|
+
expensive one. This is the entire argument for the expert registry.
|
|
71
|
+
|
|
72
|
+
Confirmed independently on a real workload — the `triage` expert in
|
|
73
|
+
[`examples/experts.toml`](examples/experts.toml), 5 support tickets, warm-up
|
|
74
|
+
enabled (n=1 run):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
t1 cache_read= 0 $0.01311 <- cold, creates the entry
|
|
78
|
+
t2 cache_read=1504 $0.00469
|
|
79
|
+
t3 cache_read=1504 $0.00454
|
|
80
|
+
t4 cache_read=1504 $0.00469
|
|
81
|
+
t5 cache_read=1504 $0.00520
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2.8x on a prompt a quarter the size of the synthetic one. The effect scales
|
|
85
|
+
with system-prompt length.
|
|
86
|
+
|
|
87
|
+
### Where the cache starts
|
|
88
|
+
|
|
89
|
+
Sweeping one system prompt's length, one call per size (n=1 per row):
|
|
90
|
+
|
|
91
|
+
| system prompt chars | total `input_tokens` | `cache_creation` | cached? |
|
|
92
|
+
|---:|---:|---:|:--|
|
|
93
|
+
| 285 | 354 | 0 | no |
|
|
94
|
+
| 570 | 434 | 0 | no |
|
|
95
|
+
| 1140 | 594 | 0 | no |
|
|
96
|
+
| 1710 | 754 | 0 | no |
|
|
97
|
+
| 2280 | 914 | 0 | no |
|
|
98
|
+
| 3420 | 2 | 1232 | **yes** |
|
|
99
|
+
| 5700 | 2 | 1872 | yes |
|
|
100
|
+
| 14820 | 2 | 241 (+4191 read) | yes |
|
|
101
|
+
|
|
102
|
+
The transition sits between **914 and 1232 total prompt tokens**, consistent
|
|
103
|
+
with the 1024-token minimum Anthropic documents for Sonnet and Opus. Haiku's
|
|
104
|
+
documented minimum is 2048; **that has not been measured here.**
|
|
105
|
+
|
|
106
|
+
Two things this table shows that are easy to miss:
|
|
107
|
+
|
|
108
|
+
- The threshold applies to the **whole prompt**, not the system prompt alone.
|
|
109
|
+
A locked-down call carries ~150 tokens of harness before your text.
|
|
110
|
+
- Once a cache entry exists, `input_tokens` collapses to **2**. Almost the
|
|
111
|
+
entire prompt moves into the cache accounting.
|
|
112
|
+
|
|
113
|
+
### Chars per token, and why the lint under-estimates
|
|
114
|
+
|
|
115
|
+
The sweep above gives 2.78–3.04 chars/token, but it repeats one sentence, and
|
|
116
|
+
repetitive text tokenises unusually well. The structured `triage` prompt —
|
|
117
|
+
markdown headings, short lines, lists — measured **2.2 chars/token** (2976
|
|
118
|
+
chars against ~1354 system tokens).
|
|
119
|
+
|
|
120
|
+
`convene experts lint` uses 2.9, which therefore *under*-estimates real
|
|
121
|
+
prompts. That is deliberate: a false "might be too short to cache" warning
|
|
122
|
+
costs nothing, while false reassurance costs money on every call.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 3. Concurrency runs much further than "about 5"
|
|
127
|
+
|
|
128
|
+
Identical trivial calls, one burst per row, no retries (n=1 burst per row):
|
|
129
|
+
|
|
130
|
+
| n | wall | per-call min / med / max | errors |
|
|
131
|
+
|---:|---:|---:|---:|
|
|
132
|
+
| 1 | 6.3s | 6.3 / 6.3 / 6.3 | 0 |
|
|
133
|
+
| 5 | 6.8s | 6.4 / 6.6 / 6.8 | 0 |
|
|
134
|
+
| 10 | 9.9s | 7.0 / 7.2 / 9.8 | 0 |
|
|
135
|
+
| 20 | 12.1s | 8.9 / 10.4 / 12.0 | 0 |
|
|
136
|
+
|
|
137
|
+
Throughput climbs from 0.16 to 1.65 calls/second — about **10x** — with zero
|
|
138
|
+
rate-limit errors. Latency degrades gracefully rather than failing.
|
|
139
|
+
|
|
140
|
+
**This is not a discovered ceiling.** Nobody has found where it actually
|
|
141
|
+
breaks; 20 is simply the largest burst tested. convene defaults to 12 as a
|
|
142
|
+
deliberate midpoint that leaves headroom for the interactive Claude Code
|
|
143
|
+
sharing the same rate-limit pool. If you find the real limit,
|
|
144
|
+
[open an issue](https://github.com/YashShelar007/convene/issues).
|
|
145
|
+
|
|
146
|
+
Each concurrent call is a full Node process, so local RAM becomes the binding
|
|
147
|
+
constraint before long.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 4. Multi-turn works, two ways, and they cost differently
|
|
152
|
+
|
|
153
|
+
Three-turn conversation, "my favourite number is 41" → "+1?" → "×2?". Both
|
|
154
|
+
kinds answered 42 then 82, so both retained state (n=1 each):
|
|
155
|
+
|
|
156
|
+
| | turn 1 | turn 2 | turn 3 | total |
|
|
157
|
+
|---|---:|---:|---:|---:|
|
|
158
|
+
| `LiveSession` (one hot process) | 3.2s / $0.00099 | 2.3s / $0.00113 | 5.8s / $0.00130 | **$0.00342** |
|
|
159
|
+
| `Session` (`--session-id` + `--resume`) | 3.6s / $0.00257 | 3.4s / $0.00840 | 3.2s / $0.00388 | **$0.01485** |
|
|
160
|
+
|
|
161
|
+
The hot process was **4.3x cheaper** for the same conversation, because
|
|
162
|
+
`--resume` re-establishes context from disk on every turn.
|
|
163
|
+
|
|
164
|
+
Use `Session` anyway when the conversation must outlive the program — it is on
|
|
165
|
+
disk and resumable tomorrow, from any working directory since Claude Code
|
|
166
|
+
2.1.223. Use `LiveSession` when it does not.
|
|
167
|
+
|
|
168
|
+
### The cost-reporting trap
|
|
169
|
+
|
|
170
|
+
The two paths report `total_cost_usd` differently, and neither documents it:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
LiveSession 0.000987 -> 0.002121 -> 0.003417 cumulative per session
|
|
174
|
+
Session 0.000990 -> 0.001130 -> 0.001300 per call
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Summing the raw field across a live session's turns **triple-counts**.
|
|
178
|
+
convene's `Turn.cost_usd` is always the incremental cost; the raw figure stays
|
|
179
|
+
on `turn.result.cost_usd`. Pinned by
|
|
180
|
+
[`tests/test_sessions.py`](tests/test_sessions.py).
|
|
181
|
+
|
|
182
|
+
Input tokens grow as history accumulates (299 → 363 → 417 on a trivial
|
|
183
|
+
conversation). A long session is a growing prompt, not a free one.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 5. Auth failure does not look like a failure
|
|
188
|
+
|
|
189
|
+
The single nastiest behaviour in this interface. On an expired or missing
|
|
190
|
+
login, the CLI returns:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{"subtype": "success", "is_error": true, "result": "Not logged in · Please run /login"}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
…and exits **0**.
|
|
197
|
+
|
|
198
|
+
Neither the exit code nor `subtype` catches it. `is_error` is the only reliable
|
|
199
|
+
signal, which is why `decode_envelope` checks `subtype != "success" or
|
|
200
|
+
is_error`. That same condition also catches `error_*` subtypes such as
|
|
201
|
+
`error_max_structured_output_retries`.
|
|
202
|
+
|
|
203
|
+
Do not "simplify" it to `subtype == "success"`. Pinned by
|
|
204
|
+
[`tests/test_runtime.py`](tests/test_runtime.py).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 6. The lockdown flags are worth ~88x
|
|
209
|
+
|
|
210
|
+
Measured on an identical one-word prompt (n=1, carried forward from this
|
|
211
|
+
project's predecessor and re-checkable with `convene doctor --lockdown`):
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
locked down in= 144 cache_create= 0 cost=$0.00051
|
|
215
|
+
plain -p in= 2 cache_create= 9504 cost=$0.04478 88x
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A default `claude -p` loads the full agent system prompt, every tool
|
|
219
|
+
definition, your skills, MCP servers, project settings and `CLAUDE.md`. The gap
|
|
220
|
+
depends on how much you have configured, so re-measure on your own machine:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
convene doctor --lockdown
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## What has not been measured
|
|
229
|
+
|
|
230
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
231
|
+
|
|
232
|
+
1. **The real concurrency ceiling.** n=20 is the largest burst tried.
|
|
233
|
+
2. **Haiku's cache threshold.** Documented as 2048 tokens; unverified here.
|
|
234
|
+
3. **Cache TTL on this path.** Warm reads were ~30s apart. How long an entry
|
|
235
|
+
survives is unknown.
|
|
236
|
+
4. **Linux and Windows.** Every number above is macOS arm64.
|
|
237
|
+
5. **Rate-limit behaviour at the weekly cap.** No measurement of what a
|
|
238
|
+
subscription limit looks like in the envelope.
|
|
239
|
+
6. **Whether `--bare` has become the `-p` default** in any build after 2.1.237.
|
convene-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yash Shelar
|
|
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.
|