Scope-aware data tools
analyseData and processData run model-written JavaScript over any combination of the agent's entries — places,
routes, traffic incidents, custom polygons, traffic area analytics and your own GeoJSON. Each entry kind reaches the code
as one per-entry record, such as placesByEntry; Code generation
lists them all. For your own GeoJSON, the model picks property keys from the entry's
data profile rather than guessing.
Documenting every kind makes these tools large. Scope narrows each tool to the kinds a turn touches; this page explains how, and how to make your own tools scopable.
Why scope exists
A tool that documents all six input kinds, with each kind's schema, is thousands of tokens — paid on every turn that
offers it, even when the turn touches one kind. When a user asks "chart the places by category", only places matters,
so the other five kinds needn't appear at all.
The model just sees a smaller tool for that turn. A scoped tool also gets the cross-kind cheat-sheet and the
routeUtils and cluster helper docs; unscoped, it documents every enabled kind without them.
Three default tools are scopable: analyseData, processData and clusterIncidents. clusterIncidents covers
incidents alone until a scope adds the kinds the query filters against, such as routes.
The scope shape
import type { EntryDataKind } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';
// EntryDataKind = 'places' | 'routes' | 'incidents'
// | 'customGeometries' | 'trafficAreaAnalytics' | 'byod'
type DataToolScope = { kinds: readonly EntryDataKind[] };
All three default scopable tools use this shape, limited to the kinds left enabled by
dataEntries. EntryDataKind names data kinds, which differ from the
state-slice keys in EntryModeSliceName (routes vs routing).
Mandatory scope
The classifier must emit a valid scope for every scopable tool it picks; an answer without one fails validation. It gets two attempts, after which the tool keeps its unscoped surface for that turn — larger, but fully functional.
With classifier: false, scoping never engages and the tools always use their unscoped surface.
Observing scopes
const agent = createMapAgent(map, {
model: openai('gpt-4o'),
onClassify: (result) => {
console.log('tools:', result?.activeToolNames);
console.log('scopes:', result?.toolScopes);
// e.g. { analyseData: { kinds: ['places', 'routes'] } }
},
});
Watch toolScopes across a few turns to confirm the classifier picks minimal kind sets.
Add a scopable custom tool
Custom tools opt into the same narrowing with three pieces:
- A
scopeSchema(Zod) — the scope shape your tool understands. - A
scopePrompt— a one-line hint telling the classifier how to scope the tool. - A
ToolEntryBuilderinstead of a staticToolEntry, which the agent calls again each turn with the parsed scope.
import { z } from 'zod';
import type { ToolEntryBuilder } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';
type FleetScope = { kinds: ('vehicles' | 'jobs')[] };
const fleetScopeSchema = z.object({ kinds: z.array(z.enum(['vehicles', 'jobs'])).min(1) });
const fleetAnalyseBuilder: ToolEntryBuilder<MyState, FleetScope> = ({ scope }) => ({
description: scope
? `Analyse ${scope.kinds.join(' + ')} entries via dynamic JS.`
: 'Analyse fleet data.',
inputSchema: buildFleetSchema(scope), // narrowed when scope is set
execute: executeFleetAnalyse,
scopeSchema: fleetScopeSchema,
scopePrompt: 'Emit `{ kinds: ["vehicles" | "jobs"] }` listing only the kinds the user query touches.',
});
createMapAgent<MyState>(map, {
model: openai('gpt-4o'),
state: { fleet: new FleetState() },
tools: { fleetAnalyse: fleetAnalyseBuilder },
});
The classifier reads the scopePrompt and then requires a scope whenever it picks the tool. scope is undefined when
the agent is created and on turns without a scope, so return a working unscoped entry then.
Why builder, not static entry
Only a builder can be rebuilt per turn. A static ToolEntry with a scopeSchema keeps its full schema whatever scope
the classifier emits.
Related
- Code generation — the runtime / sandbox / threat model for
analyseDataandprocessData. - How it works — where in the turn the classifier emits scopes.
- Customizing tools — non-scopable custom tools.
- State — what each entry kind looks like in state.
- Securing your agent — what the toolkit bounds per turn, and what needs a server.