raw key nested inside a message object, on a write endpoint that refuses the parsed form it returns on reads.
That gap is where integration code lives, and it is the subject of this page.
What the hand-written tool carries
The introduction shows Google’s documented Python example forusers.messages.send beside the same tool declared with Charter. Everything the hand-written one carries and the declaration does not is integration code:
- MIME assembly.
EmailMessage, the headers, the content type, and themultipart/alternativelayout the moment a draft carries HTML alongside plain text. - base64url. Encoding the message bytes URL-safely, with the padding handled.
- The wrapper. Gmail’s write endpoints take a
rawstring nested undermessage. The same resource comes back on reads as a parsedpayloadthe endpoint will not accept. One schema, two directions, declared once. - The credential.
configuretakes a provider: a static token for a script, OAuth 2.0 refresh against Google’s token endpoint for a product, a per-end-user lookup when one set of tools serves many people. The tool declaration does not change. - Error handling. A failed call raises
APIErrorcarrying the status code and the server’s ownRetry-After. The hand-written version catches it, prints it, and hands the string back to the model as a successful result. - Field policy.
drafts_createoffers the model five fields and withholds nine.payload,snippet,labelIdsand the rest are absent from the type the model is handed, not filtered out afterwards. You can print that map.
The semantic layer and the mechanical layer
A model’s native substrate is semantics. Recipient, subject, body — these are things it represents directly and fills in well. Wire syntax is not: a MIME boundary, a base64 alphabet, a casing convention are mechanical facts about a transport, and every token spent emitting them is sequential compute spent on work a deterministic machine does for free and gets right every time. The usual assumption is that this gap closes as models get better. It widens. A model optimising its internal representations moves further from wire syntax, not closer — the representation that makes it better at understanding what you meant to send is not the representation that emits a correctly padded base64url blob. The layer that translates between the two becomes more necessary as models improve, not less. That layer is a compiler: from the semantic layer, where the model works, to the mechanical layer, where the API waits. Compilers are not obsoleted by smarter programmers.What it bridges
Neither transform is clever. Both are the kind of thing that is tedious to write once, wrong in a new way each time it is written again, and free once declared. See transforms for the full pipeline, and the wire contract for body format, static parameters, and pagination.
Why a boundary
An agent is only as trustworthy as the layer between it and your real systems. That layer is the one place where a guarantee can be mechanical rather than a prompt, a score, or a policy someone wrote down — because it is the chokepoint every action crosses. Charter puts three things in the contract itself. Egress — what the model can see. The LLM’s view of a schema is computed from declarations before any request is made. A field markedMode("response_only") is not filtered out of a prompt after the fact; it is absent from the type the model is handed, so it cannot reach a context window at all. Field-level data minimisation toward your model vendor, by construction. It is also printable.
Action — what the agent can send. Path, query, and body routing, wire encoding, and key casing are all resolved from the schema by a deterministic runtime. The model supplies meaning; it never assembles a request. It cannot emit malformed base64 or a field the endpoint doesn’t accept, because it never touches either.
Change — what stays true when the model changes. Behaviour lives in types and tests rather than in accumulated prompt folklore, so the mechanics of a tool are identical across models, and every pack is held to that mechanically. What remains model-dependent is whether a given model fills the schema well — which is a thing you measure and re-run rather than rewrite.
The declaration mirrors the reference page
A Charter schema is meant to be boring: every field the endpoint documents, the reference page’s own sentence as each description, the documented bounds as constraints, and the name the API documents on the wire. Written that way, a declaration can be read side by side with the page it came from, which is what makes a pack reviewable by someone who did not write it. Everything you want to be true beyond what that page says is a separate declaration rather than an edit to its text.Mode decides who sees a field. Format decides how it is encoded. WireName fixes a spelling no casing convention reaches. ConflictsWith states a rule the prose states and the API answers with a 400. Gloss carries the sentence the reference page does not contain: “Cents, not dollars: $15.00 is 1500.”
So any knowledge of your own goes in a gloss. Not in the description, which is the vendor’s text and has to stay diffable against their page. Conformance cannot compare a description to the vendor’s live page, which is drift and needs their published spec. It does check that none of your own sentences have been mixed into one, which is what keeps that comparison possible at all.
Protocols churn; contracts compile
An API’s transport is not its meaning. That Gmail carries a message as base64url underraw is a fact about Gmail’s HTTP surface today. That an email has a recipient, a subject and a body is a fact about email. The first moves on someone else’s schedule; the second does not.
A contract declares the second and derives the first. When a transport detail moves — a casing convention, a newly required header, an envelope wrapped around the response — the change is a line in a declaration. Written by hand, the same change is an edit spread across every tool function that assembled a request itself, and across whatever prompt text was teaching a model to compensate.
This is also why Charter is not built on any vendor’s SDK. OAuth 2.0 refresh is a protocol with a specification, not eleven client libraries with eleven opinions; declaring the token endpoint and the grant is smaller than adopting the library, and it does not tie the boundary to one vendor’s release cadence.
Determinism, not inference
There are two ways to stop an agent from doing something. Detect it — score the output, run a classifier, ask a second model to judge. Or prevent it — make the thing unconstructible. The guardrails industry is built mostly on the first fork. A classifier has a false-negative rate. An LLM judge has a false-negative rate and a prompt, both of which move when the underlying model is updated. Both fail in the direction of permitting the thing they were installed to stop, and both fail quietly. Charter takes the second fork. There is no scoring step in the egress path, because there is nothing to score: aresponse_only field is absent from the type the model receives, at any nesting depth. No threshold to tune, no judge to keep aligned, no evaluation set that goes stale. The same declarations the runtime executes are what egress_map prints, so the audit artifact cannot drift from the behaviour.
This covers less ground than a classifier and covers it differently. It reaches exactly as far as the contract can express — and no further. Within that reach, the guarantee is structural rather than statistical.
Compared with the alternatives
Three other ways to give an agent Gmail exist. The columns are categories rather than products, and every cell is something you can check against a pricing page, a config file, or a tool list.
A gateway also obtains the OAuth grant and stores the token. Charter does neither: getting the grant is yours, and the runtime refreshes what you hold. If you want the grant handled for you, a gateway is the right tool, and this table is not an argument against it.
The covenant
Charter runs where you run it. The library makes no network calls except the API calls you define: no telemetry, no phone-home, no traffic sampled for training. It does measure every call it makes — timings split by owner, payload against context — and hands the numbers to your process, with nowhere else to send them. “We structurally cannot see your data” is a sentence anyone can write and few can keep. It is credible only from a design whose business model does not depend on seeing it. Charter is MIT-licensed and runs inside your process against your credentials. There is no hosted component in the path, so there is nothing to opt out of and no retention policy to read.Related
- Measured results — 536 runs against live APIs, with the discarded column shown
- Quickstart — a working tool in about twenty lines
- Egress control — the boundary, and the artifact that prints it
- Conformance — what every pack is mechanically held to
- What this can’t express — the edges, named