From 0416359c6595ddf3a13b24224b47dbf9208161c7 Mon Sep 17 00:00:00 2001 From: funkpower Date: Mon, 21 Sep 2026 10:49:51 -0400 Subject: [PATCH] bindings: colophon's own question, and the reply folded back in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit colophon has composed the question about what its pages did not settle since it was written, and merged a reply back into the record it read. Both were reachable only from a terminal, so a pipeline wanting the same fallback would have had to write its own prompt — a second one, drifting from the first, asking for a shape merge does not accept. merge fills only the unsettled fields, so a model cannot overwrite what a book printed about itself. That is the property that makes asking one safe, and it belongs to colophon rather than to any caller. --- crates/bindings/python/catena/__init__.py | 4 +++ crates/bindings/python/catena/__init__.pyi | 19 +++++++++++ crates/bindings/src/python.rs | 37 ++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/crates/bindings/python/catena/__init__.py b/crates/bindings/python/catena/__init__.py index aa2c3da..86c4e2a 100644 --- a/crates/bindings/python/catena/__init__.py +++ b/crates/bindings/python/catena/__init__.py @@ -16,6 +16,7 @@ from ._catena import ( chunk_markdown, citation_prompt, clean_record, + colophon_prompt, clean_underscores, compose_subjects, count_tokens, @@ -30,6 +31,7 @@ from ._catena import ( looks_like_footnote_citations, looks_like_person, merge_citation_reply, + merge_colophon, needs_subject, pack_cues, parse_identifier, @@ -55,6 +57,7 @@ __all__ = [ "chunk_markdown", "citation_prompt", "clean_record", + "colophon_prompt", "clean_underscores", "compose_subjects", "count_tokens", @@ -69,6 +72,7 @@ __all__ = [ "looks_like_footnote_citations", "looks_like_person", "merge_citation_reply", + "merge_colophon", "needs_subject", "pack_cues", "parse_identifier", diff --git a/crates/bindings/python/catena/__init__.pyi b/crates/bindings/python/catena/__init__.pyi index 4881d97..f7d399c 100644 --- a/crates/bindings/python/catena/__init__.pyi +++ b/crates/bindings/python/catena/__init__.pyi @@ -102,3 +102,22 @@ def fetch_identifier( Blocks on the network. `None` when no register answers about that kind. """ ... + +def colophon_prompt( + record: dict[str, Any], text: str, pages: int = ... +) -> str | None: + """The question to put to a model about what the pages did not settle. + + None when the pages settled everything. Nothing is sent anywhere. + """ + ... + +def merge_colophon( + record: dict[str, Any], reply: str +) -> tuple[dict[str, Any], int]: + """A model's reply folded in. Returns (record, fields_settled). + + Only fills what the pages left unsettled; a model never overwrites what a + book printed about itself. + """ + ... diff --git a/crates/bindings/src/python.rs b/crates/bindings/src/python.rs index 69a47f1..7cc6ecf 100644 --- a/crates/bindings/src/python.rs +++ b/crates/bindings/src/python.rs @@ -434,6 +434,8 @@ fn _catena(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(read_colophon, module)?)?; module.add_function(wrap_pyfunction!(shelve, module)?)?; module.add_function(wrap_pyfunction!(fetch_identifier, module)?)?; + module.add_function(wrap_pyfunction!(colophon_prompt, module)?)?; + module.add_function(wrap_pyfunction!(merge_colophon, module)?)?; Ok(()) } @@ -509,3 +511,38 @@ fn fetch_identifier<'py>( None => Ok(None), } } + +/// The question to put to a model about the fields a book's pages did not +/// settle, or `None` when they settled everything. +/// +/// Nothing is sent anywhere: this composes the text and hands it back, so the +/// caller decides which model, on whose hardware, at what cost. The reply goes +/// to `merge_colophon`. +#[pyfunction] +#[pyo3(signature = (record, text, pages = colophon::PAGES))] +fn colophon_prompt( + record: &Bound<'_, PyAny>, + text: &str, + pages: usize, +) -> PyResult> { + let printed: colophon::Printed = from_py(record)?; + Ok(colophon::prompt::compose(&printed, text, pages)) +} + +/// A model's reply folded into the record the pages gave. +/// +/// Only the fields the pages left unsettled are filled: a model cannot +/// overwrite something a book printed about itself, which is the whole reason +/// the pages are read first. Returns the completed record and how many fields +/// the reply actually settled. +#[pyfunction] +fn merge_colophon<'py>( + py: Python<'py>, + record: &Bound<'py, PyAny>, + reply: &str, +) -> PyResult<(Bound<'py, PyAny>, usize)> { + let mut printed: colophon::Printed = from_py(record)?; + let filled = colophon::prompt::merge(&mut printed, reply) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + Ok((to_py(py, &printed)?, filled)) +}