Skip to main content
Glama
Gusmack1
by Gusmack1
README.md
# old-hands

A measured reading pipeline for AI readers of historical handwritten and typed records:
parish and Catholic registers, statutory certificates, testaments, war diaries.

It ships as a Python library, a command line, an MCP server (five `oldhands_*` tools over
stdio, usable from Claude Code, Cursor, or any MCP client) and an agent skill.

## Why it exists

An AI reader's bottleneck is perception and calibration, not knowledge. A prose skill full of
letterform tables was measured on a blind set of eighteen war-diary cells and had no effect:
the only wins came from procedure (a native-resolution crop, a sense-check of a date against
its neighbours), and the skill produced a confident wrong answer the baseline had avoided. So
this is a procedure the agent RUNS, and reference tables are consulted last.

Status: **v0.1.0, measured, no gain claimed.** On 2026-09-08 a pre-registered, blind, two-seed
A/B on 41 adjudicated cells from Scottish Catholic registers (1821-1869) gave:

| Arm | accuracy | Brier (lower is better) | image reads per cell |
|---|---:|---:|---:|
| no procedure, seed 1 / 2 | 73.2% / 75.6% | 0.202 / 0.201 | 3.3 / 3.6 |
| this pipeline, seed 1 / 2 | 73.2% / 73.2% | 0.215 / 0.234 | 7.9 / 7.0 |

The bootstrap interval on the Brier difference includes zero in both seeds. The pipeline did not
beat a plain reader on that set, and cost twice the image reads. Most skill-arm losses were
format losses (answering the procedure's glyph instead of the whole cell) and the Mc versus M'
convention; the genuinely hard cells were missed by every arm. What did hold up: model-free
staging so the expensive reader never sees a page, native-raster loading, the per-crop scan
ceiling, the leak-checked blind-set builder, the cold-read adjudication protocol (which overturned
several prior readings), and one fixed Brier convention. Use it as a harness and a staging tool;
do not use it expecting better readings until a later round shows one.

## What the pipeline does

1. `inspect`: load the page at native resolution (a PDF page yields its embedded raster,
   never a render), write `display.png` at a known scale, report whether the scan is
   thresholded (contrast views useless).
2. `candidate_set`: derive the allowed readings from context first: neighbouring dates or
   entry numbers, a gazetteer, the document class's confusion pairs.
3. `evidence`: a native crop (no resampling), an upright rotation if the page is sideways,
   CLAHE when useful, and a same-hand exemplar strip for the contested glyph. Every file lands
   inside the caller's output directory; a bundle has a size budget; a tiny crop is flagged
   `BELOW-SCAN-CEILING WxH`.
4. `compare`: two or three candidate readings, each glyph cropped beside its exemplars.
5. `commit`: a probability per candidate, or `CANNOT-READ`. A reading cannot exist without
   the comparison step; a confident choice on disagreeing views is refused.
6. `score`: accuracy and Brier (one fixed convention, abstention costs half, unreadable truth
   is scored the same for every arm), with a bootstrap interval on the delta.

Staging is model-free: `old-hands stage --cells cells.csv --out DIR` writes one small bundle
per cell and a `PROMPT_PACK.md` that any model (Grok, Sonnet, Haiku, Claude) can read; the
expensive reader only ever sees pre-cut evidence. Since v0.1.1 the pack states what the answer
string is (the boxed cell, whole and nothing more, abbreviations as inked), the two format
losses the measured run recorded. That is a prompt repair, not a measured gain; the table
above still stands until a run says otherwise.

Supported use after the null result: `stage` any cells CSV for any reader, `score` any set of
result files against an adjudicated truth set, and `inspect`/`evidence` as a native-resolution
crop tool. The `compare`/`commit` steps and the MCP server remain available but carry no
evidence of benefit.

## Install

```
uv pip install -e .            # library and CLI
uv pip install -e ".[mcp]"     # plus the MCP server
```

MCP client configuration (stdio):

```json
{ "mcpServers": { "old-hands": { "command": "uv", "args": ["run", "--project", "/path/to/old-hands", "old-hands-mcp"] } } }
```

## What the tools do and do not enforce

The tools enforce that evidence files exist before a reading is committed and that the
reading obeys the calibration rules. They cannot prove the reader looked at the files. A
harness that refuses result lines without a resolvable comparison, or a client hook, carries
that; say so in any claim.

## Tests

```
uv run python -m unittest discover -s tests -p "test_*.py"
```

Ninety-odd tests on synthetic fixtures only. No real record images, no private paths and no
personal data are in this repository, and the calibration fetcher refuses a cache inside it.

## Provenance and licence

MIT. The generic skill descends from a private reading discipline; only its hash is recorded
(`skills/reading-old-hands/PROVENANCE.yaml`) so drift is detectable without exposing it.
Design borrows, licence-clean: the MCP-plus-skill packaging of AI-Riksarkivet/ra-mcp
(Apache-2.0), the HTR-United catalogue vocabulary (CC0), and the test-time augmentation
findings of arXiv:2509.09722 (reimplemented, no code copied).