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_tools isn'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