# An orientation payload an agent can actually read

> The tool every fresh agent is told to call first had grown to ~111,000 characters — past the point where a client will inline a tool result. The orientation tool became the one thing an agent could not read. Default to a lean response with counts and pointers, gate the exhaustive form behind `verbose: true`, and assert the size in a test.

URL: https://biloh.com.au/docs/engineering-notes/an-orientation-payload-an-agent-can-read
Category: Engineering notes | Audience: agent | Updated: 2026-07-30

If your protocol tells every new agent *"call this tool first,"* that tool's response size is part of your interface contract. An orientation payload that a client will not inline is worse than a thin one: the agent either truncates it, spills it to a file it may not be able to re-read, or proceeds unoriented.

That is what happened here. A session-context tool — the documented first call, carrying tenant identity, persona, conventions and a live state snapshot — measured **111,619 characters** on a modest tenant. Roughly 90,000 of those were a single field: the full per-tool catalogue, inlined on every call.

## Why it grows without anyone deciding to grow it

Nothing about that field was wrong when it was added. A categorised map of every callable tool is genuinely useful orientation, and at forty tools it cost little. The catalogue then grew past two hundred tools, each with a name, category and one-line summary. The payload grew with it, linearly and invisibly, because **no test asserted a size and no reviewer sees a response's byte count in a diff.**

The failure only appears at the boundary — in a client's tool-result limit, not in the server. So the server's own tests stayed green while the tool became unusable in exactly the situation it was designed for: a context-free agent on its first call.

There was a second, sharper detail. The tool already had a `verbose` parameter, documented as *"reserved for future use."* Its lean mode had been designed and never built. Anyone hitting the size limit would read the parameter list, pass `verbose: false` or omit it, and receive the same enormous response — **the documented remedy was inert.** A reserved flag on a shipped interface is worse than no flag: it looks like the answer.

## The shape that fixes it

Default to the **shape** of the thing and pointers to expand it; gate the exhaustive listing behind an explicit opt-in.

```jsonc
// default — orientation, not inventory
{
  "tenant":   { "id": "…", "business_name": "…", "timezone": "…" },
  "user":     { "persona": "operator" },
  "conventions": { /* the handful of rules that prevent mistakes */ },
  "operational_state": { "active_clients": 33, "jobs_this_week": 30 },
  "tool_categories": { "clients": 13, "contractors": 12, "jobs": 4, "invoices": 6 },
  "deep_dives": [ "search_tools(query) finds a tool by intent", "…" ]
}
```

`tool_categories` — category to count — tells the agent the toolkit's *shape* in a few dozen characters where the full map took ninety thousand. It answers the only question orientation needs to answer ("is there a jobs surface here, and roughly how deep?"), and the follow-up is a different call: intent search for a specific tool, or the flat catalogue for the authoritative list. Long prose fields get a cap and a pointer rather than truncation-by-accident.

`verbose: true` returns the full map, unchanged, for the caller that genuinely wants inventory — a documentation generator, say, rather than an operator's assistant.

## Assert the budget

Size is a contract, so test it like one:

```ts
const res = await call({});                       // default shape
expect(JSON.stringify(res).length).toBeLessThan(20_000);
expect(res).not.toHaveProperty("tool_map");        // the balloon stays out of the default
expect(res).toHaveProperty("tool_categories");     // …replaced by its shape
expect((await call({ verbose: true }))).toHaveProperty("tool_map");   // detail still reachable
```

Seed the fixture tenant with enough data that the assertion means something — a hundred-plus jobs and a full contractor roster — otherwise it passes on an empty tenant and tells you nothing.

## Transferable rules

1. **A response size is an interface constraint.** If a documented "call this first" tool can exceed a client's inline limit, it has a bug regardless of its content.
2. **Default to shape, opt in to inventory.** Counts and pointers beat exhaustive lists for orientation; the exhaustive list stays one explicit call away.
3. **Never ship a reserved parameter.** Either implement it or remove it — a flag that silently does nothing is a false remedy.
4. **Cap free-text fields** with an explicit "truncated, ask for verbose" marker rather than letting a client cut them mid-sentence.
5. **Test the ceiling on realistic data.** Byte counts do not appear in code review; they appear in assertions.

## Related

- [Tool discoverability for agents](/docs/engineering-notes/tool-discoverability-for-agents) — the orient-then-search pattern this payload serves.
- [Connecting Biloh over MCP](/docs/reference/mcp-overview) — the connection and persona model.
- [Making a multi-connector MCP setup safe to act on](/docs/engineering-notes/multi-tenant-mcp-safety) — the identity fields orientation must always carry.
