liulab-llms 2026.11.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.
Files changed (73) hide show
  1. liulab_llms-2026.11.0/.github/workflows/ci.yml +109 -0
  2. liulab_llms-2026.11.0/.github/workflows/docs.yml +47 -0
  3. liulab_llms-2026.11.0/.github/workflows/release.yml +72 -0
  4. liulab_llms-2026.11.0/.gitignore +27 -0
  5. liulab_llms-2026.11.0/.markdownlint-cli2.yaml +35 -0
  6. liulab_llms-2026.11.0/.vale.ini +54 -0
  7. liulab_llms-2026.11.0/AGENTS.md +132 -0
  8. liulab_llms-2026.11.0/CHANGELOG.md +28 -0
  9. liulab_llms-2026.11.0/CLAUDE.md +1 -0
  10. liulab_llms-2026.11.0/GLOSSARY.md +159 -0
  11. liulab_llms-2026.11.0/LICENSE +21 -0
  12. liulab_llms-2026.11.0/PKG-INFO +87 -0
  13. liulab_llms-2026.11.0/README.md +63 -0
  14. liulab_llms-2026.11.0/docs/adr/0001-the-call-layers-wire.md +36 -0
  15. liulab_llms-2026.11.0/docs/adr/0002-one-process-holds-a-task.md +34 -0
  16. liulab_llms-2026.11.0/docs/adr/0003-the-state-files-layout.md +37 -0
  17. liulab_llms-2026.11.0/docs/adr/0004-the-stamps-repo-version.md +29 -0
  18. liulab_llms-2026.11.0/docs/adr/0005-the-answer-schema-validator.md +25 -0
  19. liulab_llms-2026.11.0/docs/adr/0006-the-gateway-server.md +46 -0
  20. liulab_llms-2026.11.0/docs/adr/0007-a-pass-at-scale.md +42 -0
  21. liulab_llms-2026.11.0/docs/adr/0008-off-peak-only.md +39 -0
  22. liulab_llms-2026.11.0/docs/adr/0009-jev-through-typesafes-sdk.md +38 -0
  23. liulab_llms-2026.11.0/docs/adr/0010-reviews-in-json-lines-files.md +39 -0
  24. liulab_llms-2026.11.0/docs/adr/0011-rate-limits-and-retries.md +42 -0
  25. liulab_llms-2026.11.0/docs/agents/writing.md +66 -0
  26. liulab_llms-2026.11.0/docs/api.md +83 -0
  27. liulab_llms-2026.11.0/docs/gateway.md +94 -0
  28. liulab_llms-2026.11.0/docs/index.md +94 -0
  29. liulab_llms-2026.11.0/mkdocs.yml +85 -0
  30. liulab_llms-2026.11.0/pixi.lock +4468 -0
  31. liulab_llms-2026.11.0/pyproject.toml +243 -0
  32. liulab_llms-2026.11.0/scripts/check.sh +152 -0
  33. liulab_llms-2026.11.0/scripts/conformance.py +2095 -0
  34. liulab_llms-2026.11.0/skills/install.py +271 -0
  35. liulab_llms-2026.11.0/src/labllm/__init__.py +53 -0
  36. liulab_llms-2026.11.0/src/labllm/ceiling.py +76 -0
  37. liulab_llms-2026.11.0/src/labllm/client.py +343 -0
  38. liulab_llms-2026.11.0/src/labllm/clock.py +49 -0
  39. liulab_llms-2026.11.0/src/labllm/engine.py +748 -0
  40. liulab_llms-2026.11.0/src/labllm/errors.py +83 -0
  41. liulab_llms-2026.11.0/src/labllm/flights.py +35 -0
  42. liulab_llms-2026.11.0/src/labllm/model.py +35 -0
  43. liulab_llms-2026.11.0/src/labllm/peak.py +174 -0
  44. liulab_llms-2026.11.0/src/labllm/py.typed +0 -0
  45. liulab_llms-2026.11.0/src/labllm/retry.py +63 -0
  46. liulab_llms-2026.11.0/src/labllm/reviews.py +262 -0
  47. liulab_llms-2026.11.0/src/labllm/server/__init__.py +16 -0
  48. liulab_llms-2026.11.0/src/labllm/server/__main__.py +72 -0
  49. liulab_llms-2026.11.0/src/labllm/server/app.py +328 -0
  50. liulab_llms-2026.11.0/src/labllm/server/chat.py +154 -0
  51. liulab_llms-2026.11.0/src/labllm/server/jev.py +123 -0
  52. liulab_llms-2026.11.0/src/labllm/server/limits.py +157 -0
  53. liulab_llms-2026.11.0/src/labllm/server/prices.py +85 -0
  54. liulab_llms-2026.11.0/src/labllm/server/record.py +223 -0
  55. liulab_llms-2026.11.0/src/labllm/server/settings.py +91 -0
  56. liulab_llms-2026.11.0/src/labllm/server/throwaway.py +47 -0
  57. liulab_llms-2026.11.0/src/labllm/state.py +542 -0
  58. liulab_llms-2026.11.0/src/labllm/testing.py +303 -0
  59. liulab_llms-2026.11.0/src/labllm/wire.py +199 -0
  60. liulab_llms-2026.11.0/styles/Lab/Jargon.yml +49 -0
  61. liulab_llms-2026.11.0/styles/Lab/LengthAdr.yml +14 -0
  62. liulab_llms-2026.11.0/styles/Lab/LengthDoc.yml +16 -0
  63. liulab_llms-2026.11.0/styles/Lab/Readability.yml +16 -0
  64. liulab_llms-2026.11.0/tests/conftest.py +15 -0
  65. liulab_llms-2026.11.0/tests/data/layout-1.sql +47 -0
  66. liulab_llms-2026.11.0/tests/gateway_harness.py +153 -0
  67. liulab_llms-2026.11.0/tests/harness.py +98 -0
  68. liulab_llms-2026.11.0/tests/test_call_layer.py +571 -0
  69. liulab_llms-2026.11.0/tests/test_client.py +177 -0
  70. liulab_llms-2026.11.0/tests/test_engine.py +738 -0
  71. liulab_llms-2026.11.0/tests/test_purity.py +20 -0
  72. liulab_llms-2026.11.0/tests/test_reviews.py +269 -0
  73. liulab_llms-2026.11.0/tests/test_server.py +865 -0
@@ -0,0 +1,109 @@
1
+ # The gate, run on the pull request rather than on the branch.
2
+ #
3
+ # A job exists because a pixi ENVIRONMENT differs, not because a tool differs. The six
4
+ # static steps share one environment and are one job; `test` runs in the `test`
5
+ # environment; `docs` needs the heavy `docs` environment that nothing else installs, which
6
+ # is the whole reason it is a job of its own.
7
+ #
8
+ # Every step invokes `pixi run <task>` and never a command line. The step list lives in
9
+ # `pyproject.toml` — `check-static` names it once — so adding a step is one more word
10
+ # there and nothing here. Conformance rule `workflow-step-tasks` holds every workflow in
11
+ # the repo to it, and what it checks is that the command came from the task table: a step
12
+ # may pass arguments after `--`, and the rule prints them rather than forbidding them.
13
+ # It reads the `run:` line only, so `env:`, `working-directory:`, `shell:` and a local
14
+ # composite action can still make a step do something no `pixi run` does here. Keeping
15
+ # CI and a laptop the same is a habit this file keeps, not a property anything proves.
16
+ #
17
+ # Not shipped, deliberately: a `claude.yml`, because on a public repo anyone who can
18
+ # comment could spend the API budget; container and benchmark workflows, because nothing
19
+ # here needs them.
20
+ name: ci
21
+
22
+ on:
23
+ # `pull_request` is the trigger that matters. It tests the MERGE commit — the code that
24
+ # will exist after the merge, not the branch tip — and it is the only event that
25
+ # produces a run for a pull request from a fork. `push` is for `main` alone; on every
26
+ # branch it would run everything a second time for no new information.
27
+ push:
28
+ branches: [main]
29
+ pull_request:
30
+
31
+ # Keyed on the workflow and the ref, so pushing again to a pull request cancels the run it
32
+ # supersedes. `main` runs are never cancelled: a cancelled run on the branch everything
33
+ # merges into is an unreliable gate, and a red-or-missing history is worse than a slow one.
34
+ concurrency:
35
+ group: ${{ github.workflow }}-${{ github.ref }}
36
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
37
+
38
+ permissions:
39
+ contents: read
40
+
41
+ jobs:
42
+ # ruff, pyright, vale, markdownlint and conformance, all at once, all reported.
43
+ check:
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ # A real checkout, not a source archive: `conformance` shells out to `git ls-files`
47
+ # and exits 2 without a `.git` — its "could not run", spelled apart from the 1 a
48
+ # broken rule exits, so a bad checkout never reads as a rule this repo failed. The
49
+ # default shallow depth is enough — it needs the file list, not the history.
50
+ - uses: actions/checkout@v7
51
+ - uses: prefix-dev/setup-pixi@v0.10.2
52
+ with:
53
+ environments: default
54
+ # Fail if `pixi.lock` no longer matches `pyproject.toml`, rather than silently
55
+ # solving something else. This is what makes CI run the versions the laptop ran.
56
+ locked: true
57
+ # `check-static`, not `check`: `check` is the static steps plus pytest, and pytest is
58
+ # the `test` job below, in the environment that owns it.
59
+ - run: pixi run check-static
60
+
61
+ test:
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@v7
65
+ - uses: prefix-dev/setup-pixi@v0.10.2
66
+ with:
67
+ environments: test
68
+ locked: true
69
+ # `-e test` is load-bearing: the `test` task exists in every environment, and without
70
+ # the flag pixi would run it in `default`, which this job did not install.
71
+ - run: pixi run -e test test
72
+
73
+ build:
74
+ runs-on: ubuntu-latest
75
+ steps:
76
+ - uses: actions/checkout@v7
77
+ with:
78
+ # The one job that needs history. The version is dynamic and hatch-vcs reads the
79
+ # newest tag; a shallow clone carries no tags, so the wheel would be stamped as a
80
+ # dev build even on a released commit.
81
+ fetch-depth: 0
82
+ - uses: prefix-dev/setup-pixi@v0.10.2
83
+ with:
84
+ # No `environments`: `build` is a plain task, so the default environment has it.
85
+ locked: true
86
+ - run: pixi run build
87
+ - uses: actions/upload-artifact@v7
88
+ with:
89
+ name: dist
90
+ path: dist/
91
+ # An empty upload is a green job that proved nothing.
92
+ if-no-files-found: error
93
+
94
+ docs:
95
+ runs-on: ubuntu-latest
96
+ steps:
97
+ - uses: actions/checkout@v7
98
+ - uses: prefix-dev/setup-pixi@v0.10.2
99
+ with:
100
+ environments: docs
101
+ locked: true
102
+ # `docs-build` is a task on the `docs` FEATURE, so pixi finds it in the one
103
+ # environment that has zensical and no `-e` flag is needed. The task carries
104
+ # `--strict`, so a dead link or a bad reference fails here — but not every kind of
105
+ # broken link, and not a `nav:` entry naming a missing file. The comment above
106
+ # `validation:` in `mkdocs.yml` lists what this job catches and what it does not, so
107
+ # a green run here is not a claim the site has no dead ends. This job builds the
108
+ # site and stops; publishing it is `docs.yml`, on `main`.
109
+ - run: pixi run docs-build
@@ -0,0 +1,47 @@
1
+ # Publishing the site to the `gh-pages` branch.
2
+ #
3
+ # Hand-written on purpose: zensical has no `gh-deploy` and none is planned, so the two
4
+ # halves are spelled out — build into `./site`, then force-push that directory to
5
+ # `gh-pages`.
6
+ #
7
+ # The pull request already builds the site (the `docs` job in `ci.yml`). This workflow adds
8
+ # only the publish, so it runs on `main` and nowhere else.
9
+ name: docs
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ # For re-publishing without a commit — a Pages setting changed, a deploy was lost.
15
+ workflow_dispatch:
16
+
17
+ # The deploy force-pushes a branch of this repository. Nothing else here writes.
18
+ permissions:
19
+ contents: write
20
+
21
+ # NOT cancelled in flight, unlike `ci.yml`. This job replaces a published branch, and a run
22
+ # killed between the build and the push leaves `gh-pages` holding half a site with nothing
23
+ # queued to finish it. A superseded run is cheap; a half-written public site is not.
24
+ concurrency:
25
+ group: ${{ github.workflow }}-${{ github.ref }}
26
+ cancel-in-progress: false
27
+
28
+ jobs:
29
+ deploy:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+ - uses: prefix-dev/setup-pixi@v0.10.2
34
+ with:
35
+ environments: docs
36
+ locked: true
37
+ # Writes `./site`. `--clean --strict` live in the task, so this and a laptop build
38
+ # the same way.
39
+ - run: pixi run docs-build
40
+ - uses: peaceiris/actions-gh-pages@v4
41
+ with:
42
+ github_token: ${{ secrets.GITHUB_TOKEN }}
43
+ publish_dir: ./site
44
+ publish_branch: gh-pages
45
+ # `gh-pages` holds built output and no history anyone reads: one commit,
46
+ # replaced whole each deploy. This is what `mkdocs gh-deploy --force` did.
47
+ force_orphan: true
@@ -0,0 +1,72 @@
1
+ # Publishing to PyPI.
2
+ #
3
+ # Before the first release, PyPI needs a trusted publisher for this repository, this
4
+ # workflow filename and the environment named below. That step is outside the repo, so
5
+ # `init-repo` hands you the checklist rather than leaving a workflow that looks ready and
6
+ # is not. There is no API token here, and there must never be one.
7
+ #
8
+ # Two jobs on purpose. Only `publish` holds the OIDC token that PyPI trusts, and it runs no
9
+ # code from this repository — it downloads what `build` made and hands it over. A build
10
+ # step and a publishing credential in one job means every build dependency can reach that
11
+ # credential.
12
+ #
13
+ # A repo that does not publish deletes this file; `init-repo` does that for you.
14
+ name: release
15
+
16
+ on:
17
+ # NEVER a trigger that pushing `v0.0.0` can fire — a `push:` with no branch filter,
18
+ # `create`, or a `tags:` filter that admits it. `init-repo` pushes `v0.0.0` on a repo's
19
+ # first day so the version does not read as if a release already happened, and any of
20
+ # those would fire a PyPI publish right then, from a repo nobody has finished naming.
21
+ # Publishing a GitHub Release is a deliberate act by a person, so that is the trigger.
22
+ # This paragraph is the advice and conformance rule `release-trigger` is the enforcement;
23
+ # they say one thing, so they change together.
24
+ release:
25
+ types: [published]
26
+ # For a release whose publish failed on something outside the code — a missing trusted
27
+ # publisher, a PyPI outage. Choose the tag when you run it.
28
+ workflow_dispatch:
29
+
30
+ permissions:
31
+ contents: read
32
+
33
+ jobs:
34
+ build:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v7
38
+ with:
39
+ # hatch-vcs reads the newest tag, and a shallow clone has none, so without this
40
+ # the release would be built and published as a dev version.
41
+ fetch-depth: 0
42
+ - uses: prefix-dev/setup-pixi@v0.10.2
43
+ with:
44
+ locked: true
45
+ # The same task the `build` job in `ci.yml` runs, so what is published was built the
46
+ # way every pull request already proved.
47
+ - run: pixi run build
48
+ - uses: actions/upload-artifact@v7
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+ if-no-files-found: error
53
+
54
+ publish:
55
+ needs: build
56
+ runs-on: ubuntu-latest
57
+ # Name this environment in the PyPI trusted publisher too, if you set one there. Add
58
+ # required reviewers to it to put a person between a published Release and PyPI.
59
+ environment:
60
+ name: pypi
61
+ permissions:
62
+ # OIDC: GitHub mints a short-lived token, PyPI checks it came from this repository
63
+ # and this workflow. This is the only permission the job has.
64
+ id-token: write
65
+ steps:
66
+ - uses: actions/download-artifact@v8
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+ # PyPA's own pointer, which is what their trusted-publishing guide tells you to use:
71
+ # it moves with security fixes to the publishing action. `dist/` is its default.
72
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ .pixi/
2
+ # Build output: `site/` is the docs, `dist/` the wheel. `.cache/` is mkdocs' download cache,
3
+ # which mkdocstrings fills with fetched inventories. mkdocs writes a `.gitignore` holding `*`
4
+ # inside it, so it already hides itself — named here anyway, because a rule this repo relies
5
+ # on should not live in a file a dependency happens to generate.
6
+ site/
7
+ .cache/
8
+ dist/
9
+ build/
10
+ __pycache__/
11
+ *.egg-info/
12
+ .coverage
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+ .DS_Store
17
+
18
+ # Agent products. Never a blanket `/.claude/`: the skill symlinks under `.claude/skills/`
19
+ # and `.agents/skills/` are TRACKED, because `init-repo` has to be discoverable the instant
20
+ # you clone — before anyone could run the thing that runs installers. So this names the two
21
+ # machine-local files inside `.claude/` and nothing else.
22
+ .claude/settings.local.json
23
+ .claude/worktrees/
24
+ # Installer output, from `python skills/install.py --target codex|gemini`. Nobody's clone
25
+ # needs another machine's copy.
26
+ /.codex/
27
+ /.gemini/
@@ -0,0 +1,35 @@
1
+ # markdownlint reads structure; vale reads language. Both are steps of `check-static`.
2
+ #
3
+ # There is deliberately no `ignores:` list. It used to exist only to mirror a second list
4
+ # of excluded paths kept elsewhere, and two lists that must agree are a drift waiting to
5
+ # happen. Every markdown file in the repo is linted, agent-facing ones included.
6
+ #
7
+ # `gitignore: true` is not that list coming back: it delegates to the one exclusion list
8
+ # the repo already has, so `.pixi/`, `site/`, `dist/` and `build/` are skipped without
9
+ # anything new to keep in sync. It is load-bearing — without it the glob below walks
10
+ # `.pixi/envs/`, finds 376 markdown files shipped inside node_modules and reports 5010
11
+ # issues in other people's READMEs.
12
+ gitignore: true
13
+
14
+ # The glob lives here rather than on the pixi task line so that the file set does not
15
+ # depend on whether the task shell expands `**` before markdownlint-cli2 sees it.
16
+ globs:
17
+ - "**/*.md"
18
+
19
+ config:
20
+ default: true
21
+ # Scoped, not disabled. A hard tab in prose is an accident; inside a fenced block it is the
22
+ # content — `docs/research/` quotes a tab-indented record verbatim, the reason `Lab.Jargon`,
23
+ # `Lab.Readability` and the agent-docs table already exempt it.
24
+ MD010:
25
+ code_blocks: false
26
+ # The formatter owns line length in code and nothing owns it in prose. Wrapping is a
27
+ # matter of taste, and tables and long links routinely exceed any limit, so MD013 would
28
+ # fire on every one of them.
29
+ MD013: false
30
+ # Scoped, not disabled. A Keep a Changelog file gives every release the same section
31
+ # names, so `### Added` recurs once per release by design and the default fails a
32
+ # correct changelog on its SECOND release. `siblings_only` still fails one release
33
+ # repeating its own section, which is the duplicate that is a real mistake.
34
+ MD024:
35
+ siblings_only: true
@@ -0,0 +1,54 @@
1
+ # Rules live in styles/Lab/, tracked — so `styles/` is not in .gitignore.
2
+ #
3
+ # EVERY RULE IS NAMED IN EVERY SECTION. Vale's `*` matches `/`, so sections overlap and
4
+ # per-rule settings accumulate, later sections winning. An omitted rule keeps whatever an
5
+ # earlier section said, which once left ADRs checked by nothing on green CI. Broad section
6
+ # first. Each [tool.liulab.agent-docs] key appears verbatim in one header below; conformance
7
+ # rule 3 pairs them by substring. The caps are explained in docs/agents/writing.md.
8
+
9
+ StylesPath = styles
10
+ MinAlertLevel = error
11
+
12
+ # 1. Human-facing: the published site. Uncapped.
13
+ [{README.md,CHANGELOG.md,docs/**/*.md}]
14
+ BasedOnStyles = Lab
15
+ Lab.Jargon = YES
16
+ Lab.Readability = YES
17
+ Lab.LengthDoc = NO
18
+ Lab.LengthAdr = NO
19
+
20
+ # 2. Agent-facing: word budget only. GLOSSARY.md is not here — see section 5.
21
+ [{AGENTS.md,docs/agents/**/*.md,skills/**/SKILL.md}]
22
+ BasedOnStyles = Lab
23
+ Lab.Jargon = NO
24
+ Lab.Readability = NO
25
+ Lab.LengthDoc = YES
26
+ Lab.LengthAdr = NO
27
+
28
+ # 3. Decision records: the tighter cap.
29
+ [docs/adr/**/*.md]
30
+ BasedOnStyles = Lab
31
+ Lab.Jargon = NO
32
+ Lab.Readability = NO
33
+ Lab.LengthDoc = NO
34
+ Lab.LengthAdr = YES
35
+
36
+ # 4. Research notes: uncapped, and exempt from jargon because a note quotes what it argues
37
+ # against — the note defining the jargon map lists every banned phrase as evidence.
38
+ [docs/research/**/*.md]
39
+ BasedOnStyles = Lab
40
+ Lab.Jargon = NO
41
+ Lab.Readability = NO
42
+ Lab.LengthDoc = NO
43
+ Lab.LengthAdr = NO
44
+
45
+ # 5. The glossary: no file cap. It grows one term at a time and never shrinks, so a file-level
46
+ # word count measures the size of the domain, not the quality of the writing. Conformance caps
47
+ # each entry at 200 words instead — the unit that matches how the file grows. Last because it
48
+ # is the narrowest pattern here, and the later section wins.
49
+ [GLOSSARY.md]
50
+ BasedOnStyles = Lab
51
+ Lab.Jargon = NO
52
+ Lab.Readability = NO
53
+ Lab.LengthDoc = NO
54
+ Lab.LengthAdr = NO
@@ -0,0 +1,132 @@
1
+ # liulab-llms
2
+
3
+ Lab repos call language models through one lab gateway, which holds the provider keys,
4
+ caches every response for the whole lab, and refuses a call that would cross its meter's
5
+ ceiling. This package is the client for it, the gateway itself under the `gateway` extra,
6
+ and an engine for model work over many units. seqforge is the first user, not the only one.
7
+ Distribution name **`liulab-llms`**, import name **`labllm`**.
8
+
9
+ Easy to get wrong: the cache key is the hash of the request form `labllm.wire` builds, so
10
+ nothing that labels a call (meter, tag, person, repo, ceiling) may enter the form. The plain
11
+ install never imports LiteLLM; a test pins it.
12
+
13
+ ## Restraint
14
+
15
+ Generalizable, lightweight, uncustomized — in that order, and ahead of thorough.
16
+
17
+ Every gate, lint rule, cap and check is paid by everyone who works here afterwards. They
18
+ arrive one at a time, each reasonable alone, and nothing measures the sum. So before adding
19
+ one:
20
+
21
+ - **Does it generalize?** A rule shaped by one situation belongs where that situation is.
22
+ Evidence that it helps there is not evidence about here.
23
+ - **What is the total?** Count what someone must already satisfy before writing any code.
24
+ - **Would a narrower rule do?** Prefer narrowing to forbidding a legitimate practice.
25
+
26
+ **A measurement outranks a hypothesis.** When a rule is shown to fire on correct work, that
27
+ is evidence; a defect it might also catch is not. Declining a rule, or removing one that
28
+ misfires, is as much a contribution as adding one.
29
+
30
+ ## Toolchain
31
+
32
+ - **pixi** is the only supported toolchain. Never bare pip, uv, or conda. `pyproject.toml`
33
+ is the single source of truth for dependencies, environments, and tasks.
34
+ - **Python 3.13**, declared by `requires-python` and the pixi pin. Write it anywhere else — a
35
+ ruff target, a pyright version — and `conformance` holds that copy to the floor.
36
+ - **hatchling + hatch-vcs**. The version comes from the newest git tag, CalVer
37
+ `vYYYY.M.PATCH`. Never hand-edit a version.
38
+ - Platforms: `osx-arm64` and `linux-64`.
39
+
40
+ ## Layout
41
+
42
+ ```text
43
+ src/labllm/ the package: client.py, wire.py (request form, cache key), testing.py,
44
+ engine.py, state.py (state file), reviews.py, server/ (the gateway)
45
+ tests/ pytest; test_call_layer.py runs against every gateway in conftest.py
46
+ docs/ the published site; docs/adr/ and docs/agents/ are agent-facing
47
+ skills/ repo-local agent skills; `python skills/install.py --help`
48
+ scripts/check.sh the gate runner
49
+ GLOSSARY.md the glossary — the words this repo uses
50
+ ```
51
+
52
+ ## Gates
53
+
54
+ `pixi run check` must be green before you commit. It is `check-static` plus `test`:
55
+
56
+ | Step | Tool |
57
+ | --- | --- |
58
+ | `lint`, `fmt-check` | ruff |
59
+ | `typecheck` | pyright, `standard` mode, plus annotated parameters outside `tests/` |
60
+ | `vale`, `markdownlint` | the writing rules |
61
+ | `conformance` | the repo still matches the template's rules |
62
+ | `test` | pytest |
63
+
64
+ `check.sh` runs every step and reports **all** failures, not just the first — so read to the
65
+ bottom before fixing anything. The docs build is not part of `check`; it needs its own pixi
66
+ environment and runs as its own CI job.
67
+
68
+ Work on a branch and merge through a pull request: `pull_request` tests the merge commit,
69
+ while a push to `main` tests it only once it has landed.
70
+
71
+ Four traps in the gate:
72
+
73
+ - **`--doctest-modules` runs every `Examples` block in `src/` as a test.** An example that
74
+ downloads, shells out or needs a large file wants `# doctest: +SKIP`, and the marker covers
75
+ only the example it sits on.
76
+ - **`filterwarnings = ["error"]`.** Each tolerated warning gets a targeted entry in
77
+ `pyproject.toml` with a comment saying why. Never a blanket ignore.
78
+ - **`src/` is collected**, so every module is imported at test time. Keep a heavy import
79
+ inside the method body that needs it, not at module scope.
80
+ - **A substring of `--help` output is not a substring of the help.** Typer prints it through
81
+ rich, which styles the first dash of an option on its own, so `--version` is several spans
82
+ and not one string. Colour is off under `ssh` and on in CI; reproduce CI with
83
+ `FORCE_COLOR=1`, and strip the styling before you assert.
84
+
85
+ ## Writing rules
86
+
87
+ Three rules, all enforced by `vale`: be concise; agent-facing documents have word caps;
88
+ human-facing prose avoids jargon and stays readable. Read `docs/agents/writing.md` before
89
+ writing either kind — the caps are lower than you expect, and this file is subject to one.
90
+
91
+ ### Comments and docstrings are short
92
+
93
+ Nothing checks these, so the rule is on you. A docstring says what a thing does and what it
94
+ promises. A comment says why a line is surprising. Neither is a notebook.
95
+
96
+ **Do not record:**
97
+
98
+ - **Measurements.** Sizes, row counts, timings, percentages, entry totals. They were true
99
+ once, on one machine, against one release. A number in a comment is wrong by the next
100
+ release and nothing checks it.
101
+ - **The environment.** Hostnames, absolute paths, package versions, which host something ran
102
+ on, what a tool printed that day.
103
+ - **Implementation detail a reader can see.** Restating the code below in prose, or narrating
104
+ how the current version happens to work inside.
105
+ - **History.** What a previous version did, what was tried, which ticket decided it, what the
106
+ alternative was.
107
+
108
+ Where a fact genuinely has to survive, it has a home that is checked. A comment is the one
109
+ place that holds none of them accountable, which is why it is the wrong place:
110
+
111
+ | The fact | Where it lives |
112
+ | --- | --- |
113
+ | What the code does | a test pins it |
114
+ | A decision and its trade-off | an ADR |
115
+ | Vocabulary | `GLOSSARY.md` |
116
+ | What is still open | an issue |
117
+ | A measurement | nowhere — delete it |
118
+
119
+ Keep a docstring to its contract — arguments, return, what it raises, one example if the
120
+ example earns its keep. Say the surprising thing in one sentence and stop.
121
+
122
+ ## Read next
123
+
124
+ | When | Read |
125
+ | --- | --- |
126
+ | Before changing code | `GLOSSARY.md`, then any ADR covering the area |
127
+ | Before changing the wire or a gateway | `docs/adr/0001-the-call-layers-wire.md`, `tests/harness.py` |
128
+ | Writing anything | `docs/agents/writing.md` |
129
+
130
+ The issue tracker, triage labels and domain docs are the lab defaults, the page `/lab-setup`
131
+ points every agent at. A page of this repo's own under `docs/agents/` overrides only that one
132
+ default, so write one only where this repo differs.
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ Every change worth knowing about, newest first. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version numbers are CalVer tags
5
+ of the form `vYYYY.M.PATCH`, and the tag is where the version comes from — nothing here
6
+ sets one.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [2026.11.0] - 2026-11-09
11
+
12
+ The first release.
13
+
14
+ ### Added
15
+
16
+ - The lab gateway, under the `gateway` extra: one service that holds the provider keys, sends
17
+ every model call, keeps every attempt in PostgreSQL and replays any request it has answered
18
+ before at no cost.
19
+ - The engine: declare a unit kind as one plain function, run a pass over many units from your
20
+ own command, stop cleanly at a ceiling, and resume by running the same command again.
21
+ - Off-peak-only passes, which send DeepSeek calls only in its half-price hours.
22
+ - The call layer: open a meter with a dollar ceiling and make blocking chat and Jev calls through
23
+ the lab gateway, with `labllm.testing.FakeGateway` standing in for it in tests.
24
+ - The review queue and reviews: list the units a person must look at, and save a person's
25
+ answer or drop to a file per kind that the repo commits to git.
26
+ - The gateway keeps every caller under each provider's rate limits, waiting rather than
27
+ refusing, and tries a busy provider again for a few minutes. Each reply says how many
28
+ attempts it took.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,159 @@
1
+ # Glossary
2
+
3
+ The words for model calls through the lab gateway, and for the work run over many units.
4
+
5
+ ## Glossary
6
+
7
+ ### Ceiling
8
+
9
+ A meter's limit in dollars, required and with no default. The gateway refuses a call whose
10
+ largest possible cost (estimated input plus the full output allowance), added to what the
11
+ meter has spent and holds for calls in flight, would cross it. A cache hit never counts.
12
+ _Avoid_: budget, cap, limit
13
+
14
+ ### Dropped
15
+
16
+ An ending a person's review gives: the unit is taken out of the work, with a note giving the
17
+ reason. No answer is kept. Like an answer from a review, it holds until the unit's inputs tag
18
+ changes.
19
+ _Avoid_: skipped, excluded
20
+
21
+ ### Exchange
22
+
23
+ One attempt at a provider, retries each their own, recorded whole in the gateway's record:
24
+ the request, the response or error, usage, cost, and the person, repo, meter and tag. A cache
25
+ hit adds an exchange with no bodies.
26
+ _Avoid_: transcript, log entry
27
+
28
+ ### Gateway
29
+
30
+ The lab's one service that sends every model call: it holds the provider keys, keeps the
31
+ record, replays any request it has answered, and enforces each meter's ceiling. A call
32
+ identical to one in flight waits for it, so a request is paid once. `Gateway` is also the
33
+ library's client for it.
34
+ _Avoid_: proxy
35
+
36
+ ### Held
37
+
38
+ A unit whose inputs are not there yet: its kind's loader raised `Held`, and the reason is
39
+ recorded. Not an ending, so it leaves the unit's current answer as it was.
40
+ _Avoid_: skipped, pending
41
+
42
+ ### Inputs tag
43
+
44
+ A string the repo's loader returns with a unit's inputs, stamped on its answer. The engine
45
+ only compares it, so the repo chooses how fine it is.
46
+ _Avoid_: hash, checksum
47
+
48
+ ### Meter
49
+
50
+ A group of calls under one ceiling, with its own identity on the gateway. Its label is for
51
+ people, so two meters with one label never share a ceiling. A pass opens one meter.
52
+ _Avoid_: session, budget
53
+
54
+ ### Off-peak only
55
+
56
+ A meter and pass setting, off by default. Its calls go to a provider only outside the peak
57
+ hours of the model's price table entry: in peak the gateway answers with the time peak ends,
58
+ and the call waits and is sent again. New calls stop a set margin before peak starts. A cache
59
+ hit is answered at once, and a model with no off-peak hours is unaffected.
60
+ _Avoid_: night mode, discount mode
61
+
62
+ ### Pass
63
+
64
+ One run of a unit kind over the keys it is given, in that order, under one meter. It records
65
+ each unit's outcome and returns counts per ending. Rerunning the same command starts a new
66
+ pass whose finished calls replay free, which is how a stopped pass resumes.
67
+ _Avoid_: run, batch, job, round
68
+
69
+ ### Rate limit
70
+
71
+ A provider's limit, set in the gateway's settings for all callers together: requests a
72
+ second, input tokens a second, or calls at once, for the provider or for one of its models.
73
+ A per-minute limit is entered as its per-second rate. A call over one waits; none is refused.
74
+ _Avoid_: quota, throttle, ceiling
75
+
76
+ ### Record
77
+
78
+ The gateway's one table of every exchange, which is also its response cache: the cache
79
+ entry for a request is its first successful exchange not retired. A bad entry is retired by
80
+ hand and stays in the record. Each meter's spend is rebuilt from it after a restart.
81
+ _Avoid_: log, transcript, cache table
82
+
83
+ ### Refused
84
+
85
+ An ending: the kind's code refused the unit with a message. No answer is kept.
86
+ _Avoid_: rejected, failed
87
+
88
+ ### Request form
89
+
90
+ What decides a call's answer, as `labllm.wire` builds it: provider, exact model name,
91
+ settings, the messages or Jev's description and questions, and the output schema. Its hash
92
+ is the cache key; labels, the tag and the ceiling are never in it.
93
+ _Avoid_: payload, prompt
94
+
95
+ ### Review
96
+
97
+ One person's recorded decision on one unit: an answer, or a drop. It is a line in its kind's
98
+ review file, made for the unit's inputs tag of that moment, and outranks the models' answer
99
+ while the tag is unchanged. Of a unit's reviews for one tag, the latest-timed wins.
100
+ _Avoid_: correction, label, annotation
101
+
102
+ ### Review queue
103
+
104
+ Every unit whose current answer is unsettled or refused, in key order: the units a person
105
+ must look at.
106
+ _Avoid_: inbox, backlog, triage list
107
+
108
+ ### Settled
109
+
110
+ An ending: the unit has an answer and every check passed, or a person overrode a failed check
111
+ with a reason. Code alone may settle a unit, unless a check it recorded failed.
112
+ _Avoid_: done, accepted
113
+
114
+ ### Stamp
115
+
116
+ What produced an answer, recorded with it: the kind and its version, the model for each
117
+ role, the inputs tag, the pass, and the calling repo's and liulab-llms's code versions.
118
+ _Avoid_: provenance, metadata
119
+
120
+ ### Task
121
+
122
+ One body of work's state: one SQLite file in a folder the calling command names, held by one
123
+ process at a time. It keeps every pass and each unit's outcome, answer, stamp and check
124
+ results per pass. Deleting the file is safe: rerunning rebuilds the models' answers from the
125
+ cache.
126
+ _Avoid_: project, database
127
+
128
+ ### Unfinished
129
+
130
+ A unit that reached no ending in a pass: its kind's code raised an error, its answer did not
131
+ fit the answer schema, or the pass stopped. The reason is recorded. Not an ending, so it
132
+ leaves the unit's current answer as it was.
133
+ _Avoid_: failed, errored
134
+
135
+ ### Unit
136
+
137
+ One independent piece of work, named by a string key, such as one sequencing Series. Its
138
+ current answer is its outcome in the latest pass that reached an ending, unless a review made
139
+ for that outcome's inputs tag outranks it.
140
+ _Avoid_: item, row, sample
141
+
142
+ ### Unit kind
143
+
144
+ A repo's declaration of one kind of unit: a name, a hand-set version, roles, a loader, one
145
+ plain function that does the work, an answer schema and answer checks.
146
+ _Avoid_: step list, pipeline
147
+
148
+ ### Unsettled
149
+
150
+ An ending: the unit has an answer that fits its schema, but a check failed. The answer is
151
+ kept with each failed check's message.
152
+ _Avoid_: doubtful, partial
153
+
154
+ ### Waiting call
155
+
156
+ A call the gateway holds back for a rate limit or a provider's pause after overload. Waiting
157
+ calls go first come, first served, holding nothing against their meter's ceiling. Waiting
158
+ for a rate limit never ends a call; only a provider still failing after the retry budget does.
159
+ _Avoid_: queued request, throttled call