Metadata-Version: 2.4
Name: handoverai
Version: 2.0.1
Summary: Handover of In-Context Learning state across session boundaries (spec 2.0-icl-handover) — writer/reader engines, guarantees, conformance suite, and agent skill.
Author-email: Abhishek Agarwal <abhishek.agr31@gmail.com>
License: Apache-2.0
Project-URL: Documentation, https://github.com/abshek/handoverai#readme
Project-URL: Source, https://github.com/abshek/handoverai
Keywords: llm,context,compaction,handover,agents,in-context-learning
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Provides-Extra: schema
Requires-Dist: jsonschema>=4.0; extra == "schema"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: jsonschema>=4.0; extra == "dev"
Dynamic: license-file

# handoverai

Handover of in-context learning state across session boundaries — a reference
implementation of spec `2.0-icl-handover`.

When a task outlives a single LLM session, the next session gets **only what you
pass on**. This library defines what that record must contain, how big it needs
to be, how to check it, and how to tell whether a post-handover failure came from
missing information, from the memory budget, or from the continuation procedure.

The record is not judged by how much it resembles the transcript. It is judged by
what the continuation can do with it. A record that reproduces earlier wording but
drops a binding constraint is a failure; a record that shares no wording with the
transcript but preserves what determines the next decision is a success.

```bash
pip install handoverai
```

Or from source:

```bash
git clone https://github.com/abshek/handoverai && cd handoverai
pip install -e .
```

## Two minutes

```bash
handoverai write draft.json --out handover.json     # build a record
handoverai validate handover.json                   # ten blocking checks
handoverai render handover.json --later-input "..."  # prompt for the next session
```

```python
from handoverai import (
    ExactEntry, ExactPart, Source, TaskSpec, LaterInputLaw,
    WriterEngine, WriterContext, ReaderEngine,
)

exact = ExactPart()
exact.add(ExactEntry(
    id="c1", kind="constraint",
    statement="Never write to the production database.",
    status="adopted", source=Source(ref="turn-14"), blocking=True,
))

task = TaskSpec(
    task_id="migration-42",
    goal="Finish the schema migration",
    target_form="unified diff",
    scoring_rule="tests pass",
    later_input_law=LaterInputLaw(description="follow-ups about the migration"),
)

record, report = WriterEngine().generate(
    WriterContext(exact=exact), task, limit_tot=8000,
)
prompt, state, log = ReaderEngine().resume(record.to_dict(), later_input="what next?")
```

## What makes this different from compaction

Most compaction shortens a prompt and hopes. This library makes four things
checkable that a summarizer leaves implicit.

**A record has three parts, and they obey different rules.**

| Part | Rule |
|---|---|
| `H_exact` | Zero loss. Verbatim, append-only, hashed. Decisions, constraints, prohibitions, rejected options *and why*, open issues, permissions — everything whose alteration changes which actions are permitted. |
| `H_stat` | Admissible **only** with an explicit relation to the task loss: `sufficient` (preserves the conditional law exactly) or `bounded` (carries a stated error bound). There is no third grade. |
| `H_residual` | The observations the statistics do not capture — a rare failure, a counterexample, an unresolved error — selected by a *stated* rule, by marginal value per bit. |

**External storage is not free.** `b_act` (in-prompt), `b_ext` (files, indexes)
and `b_tot` are reported separately, always. A short prompt achieved by pushing
text into files is not a small handover, and comparisons are made on `b_tot`.

**Repeated compaction only loses.** Summarizing a summary is monotonically lossy.
So `H_exact` is carried forward by *copy*, never re-derived, and a status change
is a new revision that supersedes the old entry rather than an edit.

**Refusals are load-bearing.** If the constraints do not fit the budget, the
writer raises instead of truncating them. If a quantized block cannot support its
error bound, the grade is refused rather than downgraded. If an external id does
not resolve, the record is incomplete — not degraded.

## Guarantees, and where they come from

The parametric path is exact. For Gaussian linear regression, `(G_n, b_n) =
(X'X, X'y)` is predictively sufficient for **every** later input, and its size is
`d(d+1)/2 + d` scalars **regardless of how many demonstrations there were**:

```python
from handoverai.stat.gaussian import sufficient_scalar_count
sufficient_scalar_count(64)     # 2144 scalars — whether n was 200 or 200,000
```

Quantize it and the block declares the predictive-KL bound it actually achieves,
with the precondition checked; if the precondition fails, the writer refuses the
`bounded` grade rather than shipping an unbounded approximation.

The nonparametric path grows, and the sizing rules say by how much. Memory is
driven by the target accuracy and the function class — not by transcript length,
and not by `n`. The sample floor and the memory floor combine by **maximum**, and
reducing intrinsic dimension is usually a larger win than any coding improvement.

## Measuring it honestly

The library is opinionated about what a number is allowed to be called.

- **Only differences of ideal risks are information loss.** With a real model as
  decoder you measure something that mixes in the continuation gap, and every
  such number is returned labelled `end2end`, not `oracle`.
- **The three terms are separable only against a known ideal decoder.** The
  Gaussian harness supplies one, so `three_term_split` reports (i) budget,
  (ii) writer, (iii) continuation gap separately. Elsewhere, (ii) and (iii) mix.
- **The deliverable is a risk curve, not a point.** `budget_sweep` and
  `risk_curve` exist because a single operating point hides where curves cross.
- **Savings are net of the writer, and quoted at matched quality.** The writer is
  usually itself a model call over the full context, and it is the largest
  overhead. `savings.py` computes the break-even call count and refuses to let
  the compression ratio stand in for a saving.

```python
from handoverai.harness import gauss
episodes = gauss.make_episodes(seed=0, count=32, d=4, n=64)
split = gauss.three_term_split(episodes, my_writer, bit_budget=2048)
# {"i_budget": ..., "ii_writer": ..., "iii_continuation": None}   <- None without a model runner
```

### The leak detector

`H-BERN` implements the pre-query penalty: a writer committing before the query
cannot beat `max{0, 1 - B/m}` bits of loss. If your measured loss falls below
that, it is impossible — something in the pipeline let the writer see the query.

```bash
handoverai harness bern --m 16
```

This catches the most common way a handover evaluation becomes meaningless: a
fixture that builds the record *after* sampling the later input.

## Using it from a coding agent

See [`agent/`](agent/) — a Claude Code skill, an `AGENTS.md` for Codex and
OpenCode, and an MCP server.

```bash
# Claude Code
mkdir -p .claude/skills/handoverai && cp agent/SKILL.md .claude/skills/handoverai/SKILL.md

# Codex / OpenCode
cat agent/AGENTS.md >> AGENTS.md

# MCP
claude mcp add handoverai -- python -m handoverai.mcp_server
```

The split is deliberate: **the model routes, the scripts compute.** Deciding what
counts as a decision versus an open issue is judgement; computing `G_n`, sizing a
budget, or hashing a payload is not, and a record whose numbers were produced
in-context cannot pass the conformance checks.

## Command line

| Command | Purpose |
|---|---|
| `write draft.json` | Build a record; refuses rather than truncating constraints |
| `validate record.json` | Ten deterministic checks (§9.4); blocking |
| `render record.json` | Build `p_H`, binding record first and verbatim |
| `hop record.json` | Next-hop draft with `H_exact` copied forward |
| `check-hops a.json b.json ...` | M1 constraint adherence across the chain |
| `budget record.json --context-size N` | M5 **and** M6, with `b_act`/`b_ext`/`b_tot` |
| `harness gauss\|cell\|bern\|sep` | Run an analytic harness |
| `arms table.json` | Check a comparison table is budget-matched |
| `migrate v1.json` | Lift a `1.0-KatoKato` payload (refuses on ungraded statistics) |
| `schema` | Print the payload schema |

Exit codes: `0` ok, `2` refused (a spec-mandated failure), `3` invalid.

## Conformance

```bash
python conformance/runner.py
```

Five reference vectors reproduced from their inputs — including the hand-checkable
`gauss-d2-n5` from the spec's Appendix D, down to the measured predictive KL of
`2.2877e-4` against its bound of `5.2190` — and eight malformed payloads that must
each be rejected by a *named* check. `conformance/canonicalization.md` fixes the
serialization so hashes are portable across implementations.

## Testing

```bash
pytest                      # P1-P14 invariants, engines, conformance
```

The property table is the spec's §12.3 in executable form. The highest-value
single test is **P3**: the matrix record and the exact synthetic sufficient
demonstrations carry identical information, so any measured difference between
them under a fixed model is *provably* a decoder gap, never information loss.
That makes the presentation swap the one ablation with an unambiguous attribution.

## Scope and honesty

- `sigma2` is treated as known in the parametric block. Unknown noise variance
  needs extra retained quantities and is **not** covered; the writer says so
  rather than claiming a grade it cannot support.
- The `beta > 1` local-polynomial extension is flagged as an extension, not
  covered by the main theorem.
- Theorem 5.10's constants are not supplied by the theorem. The floors are
  reported as shapes with `constant_calibrated: false`; a measured error below
  them is not a violation.
- Cell-statistic risk constants `C1..C4` default to 1 and must be calibrated on
  H-CELL before any epsilon derived from them is published.

## References

The full specification is in [`docs/handover_spec_v2.md`](docs/handover_spec_v2.md).
It derives from Masahiro Kato & Taka Kato, *Handover of In-Context Learning State
Across Session Boundaries*, arXiv:2608.14528v1.

## License

Apache-2.0.
