Scope tools. Stage discovery.
Register every domain once; decide per agent what each one sees. Scope is the coarse control. Discovery is how an agent with many domains keeps each request small — the model pulls in tools as the conversation needs them.
Scope the agent
makeAgent takes a domains scope: .all (the default) or .only(Set).
// sees every registered domain (the default)
let full = try runtime.makeAgent(
provider: .anthropic(apiKey: key),
role: AgentRole(staticPersona: "Full-access assistant.")
)
// sees weather and calendar only
let scheduler = try runtime.makeAgent(
provider: .anthropic(apiKey: key),
role: AgentRole(staticPersona: "Scheduling assistant."),
domains: .only(["weather", "calendar"])
)
Scoping is how one registration serves many agents — a read-only review
agent and a full editing agent share the runtime and differ in one argument.
Naming a domain that isn't registered throws
AgentKitRuntimeError.unknownDomains with the offending ids.
Scope is a snapshot
Each agent captures the registry at makeAgent time. A domain registered
afterwards appears only in agents made after the registration:
let runtime = AgentKitRuntime()
try runtime.register(WeatherDomain())
let early = try runtime.makeAgent(provider: provider, role: role) // sees weather
try runtime.register(CalendarDomain())
let late = try runtime.makeAgent(provider: provider, role: role) // sees weather + calendar
// `early` still sees only weather
Register at runtime freely — a mid-conversation registration can never change what an active agent is allowed to call.
Registration is validated
register(_:) throws AgentKitRuntimeError when ids can't coexist:
| Error | Cause |
|---|---|
.reservedDomainId |
the domain id agentkit belongs to the built-in meta-tools |
.reservedToolId |
a tool id starts with the reserved agentkit. prefix |
.duplicateDomain |
a domain with that id is already registered |
.duplicateTool |
a tool id is already registered, in any domain |
Together with .unknownDomains from makeAgent, these cover what the
runtime itself can refuse. Everything a turn can throw lives in the
error reference.
Stage discovery
Every agent carries a built-in agentkit domain of meta-tools. With many
domains, the model doesn't get every tool definition upfront — it discovers
what exists and activates what it needs, mid-conversation:
| Meta-tool | What it does |
|---|---|
agentkit.list_tools |
lists domains with capabilities, summaries, tool counts, and active status |
agentkit.activate_tools |
activates one domain, adding its tools to subsequent requests |
agentkit.list_context |
lists context sources — see inject live state |
agentkit.inspect_context |
fetches one context source's detail |
A discovery turn reads like this:
user: "What's the weather in Paris?"
model → agentkit.list_tools
← weather (readOnly, 1 tool) — inactive
model → agentkit.activate_tools {"domain": "weather"}
← Activated domain 'weather' with tools: weather.current
model → weather.current {"city": "Paris"}
← Paris: 18C, sunny
model: "It's 18°C and sunny in Paris."
Each request carries the meta-tools plus only the domains activated so far, so prompt size scales with what the conversation uses — not with everything you've registered. Domain summaries do the selling here: the model picks which domain to activate by reading them, so write them for the model.
When discovery engages
The provider decides, through the discovery strategy it declares in its capabilities. You don't configure it per agent:
- Cloud providers (Anthropic, OpenAI, Gemini, your backend) discover per request — the first request offers only the meta-tools, and the set grows by activation.
- Providers that send every scoped tool upfront skip activation:
agentkit.activate_toolsisn't offered because there's nothing to activate. The other three meta-tools remain. - The on-device Apple provider sends tools upfront while the scoped set fits its 20-tool limit. Register more than fits and the session falls back to staged discovery automatically — an activation that would blow the limit is rejected, and the model is told.
Where the tool budget is small, scope tightly with .only and let discovery
handle the rest.
Next
- Define tool domains — manifests, schemas, executors, outcomes.
- Inject live state — the context half of the meta-tools.
- Bound the turn — caps on tool calls and round trips.