Mode is the pack author’s lever. It is declared on a
field, and it is fixed when the pack is written. A projection is the other half,
and it belongs to whoever deploys the tool.
You did not write documents_batch_update and cannot edit it. You are the one
who knows that this agent may insert text and must never delete a range.
projection.py
extra="forbid". Only the view narrows.
Why the tool is shaped this way
The Google Docs API has three endpoints, and one of them carries every write.documents.batchUpdate takes a list of requests, each setting exactly one of 33
kinds of edit. Charter models all 33, which is what makes the tool complete and
also what makes it cost 8,183 tokens of schema before the model reads the task.
Narrowing to three members takes that to 1,750, or 79% smaller.
The permission matters more than the tokens. Google’s documents scope grants
every one of those 33 edits as a single indivisible grant. There is no scope for
“may edit text, may not delete content”. The union member is the capability, so
pruning it is the only place that permission can be expressed.
keep and drop
keep selects within the sibling group it names. Keeping three members of the
union drops the other 30 and leaves document_id and body.write_control
alone, so the tool is still callable.
drop removes one path outright:
drop.py
DeclarationError when you build the
projection, not when the API answers 400.
pin
pin removes a field and sends a fixed value on every request. The field is
neither visible to the model nor reachable by it:
pin.py
Path(),
Query() or Body()
marker says, picks up the Format transform, the
key casing cascade and the percent-encoding, and a single body field still
unwraps to become the body. A value that does not satisfy its field raises
DeclarationError when you build the
projection.
Pin top-level fields. A nested path raises.
Naming a path
Tool.paths() lists what a projection can select,
one level at a time:
paths.py
selectors.py
index names 19 different fields, and naming it raises an error
listing the candidates rather than guessing.
What a path costs
A list of names does not say which branch is expensive, and on a large tool that is the only thing you need to know.search_issues in the Linear pack has one
top-level field. Every projection worth writing lives underneath it.
Pass by_cost=True to price the level instead of naming it, most expensive
first:
by_cost.py
narrow.py
by_cost reports is what drop produces. Sizes
are schema_tokens(), serialised
JSON characters over four.
Pricing costs one schema generation per path, so only the level being returned
is priced. Pass a prefix to drill, or depth=2 to price a level and its
children in one call.
Four properties to read the numbers by. Each is something drop really does,
so the number is the warning:
- Costs do not sum to the total. Where two fields share a
$def, dropping either one alone leaves it in place, so both price cheap and dropping both is worth more than the sum of the two. - A negative cost means the drop makes the tool larger. Pruning inside a model
several siblings share splits one
$definto per-path copies. Droppingbody.requests.insert_text.location.indextakesdocuments_batch_updatefrom 8,183 tokens to 8,435, for a tool that can do less. - A cost of zero means
Modealready removed the path, so a projection naming it does nothing.events_insertpricesevent.i_cal_uidat 0 undermode="write";events_importprices the same path at 103. - A required path is priced even though
droprefuses it.documents_batch_updatecannot losebody.requests, and the price is what says whetherpinis worth reaching for.
What is enforced
A projection can only remove, and removal is checked by the schema rather than requested in a prompt:enforced.py
Request oneof rule still rejects a request that sets two edits at once.
Composition
Deriving from a projection narrows what is left:compose.py
Reading the boundary
Both halves print inegress_map(), which reads the
same declarations the runtime executes:
egress.txt
Where projections live
In your code, not in the pack. Charter ships the mechanism and no named projections, because a projection is a policy decision and the policy is yours.gdocs.documents_edit_text in a pack namespace would be Charter deciding what
text editing means for every deployment that installs it.
Keeping them in your repo also puts them in your pull requests, next to the rest
of what your agent is allowed to do.
Related
- Context window — finding which tool is worth narrowing, and what it was worth
Tool.derivedandTool.paths- Egress control for what the map reports
- The mode system for the author-side half
DeclarationErrorfor what a bad selector raises