Skip to main content
A selection request is two halves: the records you could send, and the task they have to serve. Everything else is a budget.

Records

text is the only required field. Everything else is a signal, and each one earns its place:
Do not move content into attributes to avoid the meter.These three fields are bounded by the published size cap. Values beyond it are outside the contract and may be rejected: a payload built around stuffing them is not an allowance the contract grants.It also does not do what you would want in the meantime. These fields are read (the engine folds them into the text it infers entities, keywords and tags from), but they are indexing hints, not content: nothing parked there can be selected, excerpted or returned. Keep them short and structured, and put the content in text.

Record order is part of the request

The same request under the same operating_point returns the same answer. That guarantee is scoped to the request exactly as sent, and the order of records is part of it: two requests that differ only in record order can keep different records under a tight budget. Send a stable order if you want stable results. The order your own store returns them in is usually fine, as long as it does not change between calls for reasons unrelated to the content.

Record kinds

A kind outside this list is rejected with a 400, before the engine runs and without a charge (rejected values).
tool_schema today behaves like any other record. A protected-kind behaviour exists for it (never dropped, never lossily excerpted), but it is not enabled on the operating points serving today. Label your schemas correctly anyway: protection is enabled per operating point, and mislabelled records will not benefit when it is.

Records are sent every time

There is no upload step and no index. The records ride in on each request and are gone when it returns. That is what makes the service stateless. It is also what the cost model follows: the meter counts what you send, every time you send it. So if the same 40 documents accompany every call in a loop, that is 40 documents metered on every call. Worth designing around from the start: pre-filter with your retriever, or keep a working set. It is not worth working around by splitting one call into several, which costs more rather than less. The same records also count against your per-request ceiling today, which is live.

Tasks

Nothing in the request pins a record today

required_record_ids is the field designed to do it: a constraint the engine satisfies, as against importance, which is only a prior it weighs.
It does not pin today. Ids you send are matched against the engine’s own internal record identity rather than against your records[].id, so they currently match nothing. importance was never the pin either: turning it up to 1.0 is a strong hint and not a guarantee.So if a record must reach the model, do not send it to be selected. Keep it out of records[] and concatenate it around rendered_context yourself:
That is the right pattern for a system prompt, a schema the model must call against, and the current user turn, and it stays right after the field is wired, because those things do not benefit from being selected anyway.
Keep sending required_record_ids for the records where a pin is a preference rather than a requirement. It costs nothing and starts working without a change on your side.

recent_prompts costs allowance without affecting selection

The field is accepted and is intended as a drift signal for the engage or stand down decision.
It does not affect selection. The service accepts the field and does not pass it into selection. It does count against your per-request ceiling and it is metered, so a long history costs you allowance and changes nothing about what you get back.Send a handful, or none. Do not send the whole transcript.

Three mistakes that cost you quality

It is the default, so it is what you get if you never think about kind. But a tool result, a schema and a retrieved page are treated differently, and flattening them into one role discards a signal that costs you nothing to supply.
Without id, selected[].record_id is a server-generated value you cannot join back to anything, so you can render rendered_context but you cannot reason about which of your objects survived. Supply ids from the first call.
Selection works at record granularity. One 200 KB record is one keep-or-drop decision, so there is nothing to select between: you get pass-through or nothing. Split along the boundaries that already exist in your system: one tool result, one document, one turn.

What comes back

selected[] carries, per record: record_id, kind, title, source, score, reason, excerpt, excerpt_format and selected_tokens.
  • excerpt is extractive. It is built from your record’s own content. Nothing is paraphrased or summarised into new prose, and no model writes replacement text. What comes back is your material, or some of it. It is not guaranteed to be a byte-identical span of your input. Four things an excerpt may do, any of which breaks an exact-bytes assumption: Treat excerpt_format as an open set. plain is text; any other value indicates a re-encoding, and the safe fallback is to handle the value as text. If you need exact bytes, because you are resolving citations back to offsets or re-parsing an excerpt as JSON, join on selected[].record_id and use your copy of the record. That is what the ids are for, and it is the supported way to get byte fidelity.
  • score is comparable within one response, and is not calibrated across requests or across operating points. Rank with it; do not threshold on an absolute value.
  • reason is open-ended. Show it in a debugging view, do not branch on it.
Set return_per_record: false if you only want rendered_context and the token arithmetic: the response body gets materially smaller.