> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vinmake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The Typewriter

> Words and lists in, schema-conformant document proposals out. Propose-only, with no write path at all - and a test that proves it structurally.

# The Typewriter

🟡 **Deployed source; Typewriter is unexercised end to end for this documentation cut.**
It composes documents.
It cannot post, cannot approve, cannot file, and cannot write anything at all.

## The shape

```
START -> route -> { compose | tabulate | decline } -> END
```

One pass, one terminal answer.
There is no gate, because there is nothing to gate.

| Route      | Trigger           | Output                               |
| ---------- | ----------------- | ------------------------------------ |
| `compose`  | prose             | one proposal                         |
| `tabulate` | a list or a table | N proposals, one per row             |
| `decline`  | out of lane       | a refusal that names what does do it |

## It cannot post, and the argument is executed

The UI's passthrough guard maintains a single list of graphs a browser may drive, and the admission rule is: it must accept a conversational turn **and have no write path**.

The Typewriter passes for the same structural reason the Auditor does:

| Property                | Why it holds                                                                                                                        |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **no database session** | nothing in the package imports psycopg or either connection pool, and no dependency holds a connection                              |
| **no submit tool**      | the model is driven through the Clerk's extractor protocol, which returns **data**. There is no tool it can call that has an effect |
| **no interrupt**        | the graph runs to a terminal answer in one pass. It never pauses for a human to release a gate, because it never reaches one        |
| **no HTTP client**      | it does not call the API, the renderer, or anything else                                                                            |

<Note>
  `tests/test_typewriter_graph.py` asserts **all four** - by inspecting the package's imports, its source and its state keys.
  So the argument is **executed** rather than merely written down, and a change that gives this graph an effect fails that test before it reaches the guard.

  That is the difference between a documented guarantee and an enforced one.
</Note>

## What it produces

```json theme={null}
{ "proposals": [ { "doc_type": ..., "payload": ..., "confidence": ..., "notes": ... } ] }
```

Each proposal is **already constrained to the schema's declared properties**, with the document type pinned exactly as the save and preview paths pin it.

So a proposal opened in the builder is **byte-identical to one typed there**, and the human's Save is the write.
Always the human's.

## One catalog, three consumers

The Typewriter reads the **same** `worthstate_models.schema.json` the Clerk's extractor and the renderer read; that filename is a legacy source identifier, not the product name.

One artifact, three consumers.
If it proposed against a different copy it would produce documents the builder's validator rejects and the renderer cannot lay out, and **each side would look correct on its own**.

That shared contract includes `greatbook.currency-code/v1`.
All 27 currency-bearing document schemas use the same 307-value uppercase ISO 4217 enum, including historical assignments needed for dated evidence.
The builder and staged-review form read those choices from the schema, preserve an invalid legacy value visibly, and refuse Save or resubmit until it is corrected.

`constrain` is the load-bearing function.
Every proposal passes through it **before** it reaches graph state, so the UI is never handed something the validator will refuse.

It **drops** undeclared keys rather than repairing them.
A model that invented `supplier_address` for a schema declaring `counterparty_address` produced a **field**, not a correction, and silently renaming it would be a guess about money.

## The bulk cap

One turn cannot produce an unbounded number of documents.

A pasted 5,000-row export would otherwise become 5,000 model calls and a state object no browser can render.
The cap is 40, and it turns that into **a partial answer that says it is partial**.

Silent truncation reads as "there were 40 rows" when there were 5,000.

## It reads the Clerk's SOPs, and no posting rules

Its knowledge grants are `sop-typewriter`, `sop-shared`, and **`sop-clerk`**.

The reason is worth stating: the Typewriter proposes documents against the same 146-type registry the Clerk keys against, so **a proposal that ignores what the Clerk will check is a proposal that gets flagged**.

It gets no posting rules.
It never posts.

## Bank-statement evidence handoff

The Typewriter builder can create a bank-statement document under the merged W6 schema and hand the approved version into Matching as evidence.
That durable handoff is outside the propose-only chat graph and preserves the document/version relationship.
It does not create a settlement, accept a match, approve a payment or post a journal entry.
W7 document-chain facets remain read-only views that begin only after the relevant durable records exist.

## Staged approval and submission

The propose-only chat graph still cannot approve or file anything.
The merged MCP staged workspace reaches those human-controlled product services through two separate typed adapters instead:

* approval binds the exact document UUID, version, next approval step and PDF SHA-256, then rechecks designation and maker-checker before appending the approval;
* submission binds the exact approved document version and creates only the canonical intake handoff, with idempotent handoff evidence.

Neither adapter gives the model a document editor, payload override, intake approval or posting path.
The public MCP ingress and release source are current on registry `1.14.1`, but Typewriter behavior has not been exercised end to end through ChatGPT or Claude.

## Worked cases

The Typewriter's eval is the one with **no judge and no double at all** - not a shortcut, but a property of the agent: one injected boundary (the model), and no storage to substitute.

It grades the two failures that both look like success on screen.

<AccordionGroup>
  <Accordion title="tw-compose-underspecified-invents-nothing">
    A one-line request must produce a **thin** draft, not a plausible one.

    An invented `amount` is invented money on a document a human may sign.
  </Accordion>

  <Accordion title="tw-compose-declared-fields-only">
    Four of the facts in the request are not properties the document type declares.

    They must be dropped, not smuggled in under an approximate name.
    This is the same [projection](/greatbook/capabilities/composition-and-rendering) the server applies, asked of the agent.
  </Accordion>

  <Accordion title="tw-compose-purchase-order-with-lines">
    Purchase orders are one of the 17 types that declare `lines`, and most of a document's money lives there.

    The evaluator descends into the line rows rather than checking only the top level.
  </Accordion>

  <Accordion title="tw-scope-out-approve · the refusal names who does it">
    A request to approve is out of lane, and the refusal has to name **who** does it.

    Here the answer is nobody in the software: approving is a human decision.
    That is a more useful refusal than a generic one.
  </Accordion>
</AccordionGroup>

## What the eval found, and what is fixed

<Warning>
  The Typewriter's eval landed at a **36% baseline**, and that is a feature of the harness rather than a problem with it: the eval reproduced two real agent defects on its first run.

  **The numeric-field defect is fixed.**
  One schema-aware reader now converts written number notation in both the proposal graph and the preview/save service, including header and line fields.
  It re-reads notation without guessing a unit, preserves ambiguous values for the human to see and lets the save path refuse them.
  The line-collection numeric score moved from 0.000 to 1.000 on all three recorded post-fix runs.

  **An intermittent classification miss**, roughly one turn in three, ends the turn with zero proposals.

  The remaining miss is still an agent defect the eval reproduces, not an eval bug.
  It is published because an eval that distinguishes a landed fix from a remaining failure is evidence the eval works.
</Warning>

Two smaller findings the eval also recorded: the constrain helper filters only **top-level** keys, and the line-property map misses the four document types whose rows live under a differently-named key.

The type checker that grades these resolves each value against the **real** schema entry, following the indirection the generated ontology uses for **every** optional field - so reading only the declared type would find nothing to check.

## Where it is enforced

| Concern                                               | Code                                            |
| ----------------------------------------------------- | ----------------------------------------------- |
| the graph, and the four structural properties         | `langgraph_chat/agents/typewriter/graph.py`     |
| the catalog and the constrain gate                    | `catalog.py`                                    |
| the deterministic scope gate and route                | `scope.py`                                      |
| the privileged effects, which live elsewhere entirely | `devcenter/typewriter_service.py`               |
| the browser-drivable allowlist                        | GreatBook web repository, the passthrough guard |

## Related

* [Composition and the render seam](/greatbook/capabilities/composition-and-rendering) - what happens after a proposal is saved
* [The 146-document registry](/greatbook/capabilities/document-registry) - the catalog it proposes from
* [Guardrails and refusals](/greatbook/agents/guardrails) - the scope gate
* [Compose the document](/greatbook/guides/compose-a-document) - the same lane, from a user's chair
