Why IntentCall is built the way it is. For usage patterns, see DX FAQ.
Q: Why is it called IntentCall and not AgentKit? A: OpenAI shipped a product named AgentKit in October 2025, creating a direct pub.dev and search collision. IntentCall — "register intents, call them everywhere" — describes the registry-plus-invoke model without overlap. See ADR 0010.
Q: What problem does IntentCall solve that dart_mcp alone does not?
A: dart_mcp is a transport implementation. IntentCall adds a typed intent registry on top: a central AgentRegistry where tools declare AgentCallEntry objects once, and adapters publish them to any transport. Without IntentCall, each adapter re-implements registration, validation, and dispatch independently.
Q: Where should canonical IntentCall design decisions live?
A: In this repository. IntentCall owns the product philosophy, platform projection model, AX/DX contracts, adapter expectations, and public direction such as IntentPack. Consumer repositories such as mcp_flutter own proof and integration guidance: hosted dependency checks, Flutter MCP Toolkit bootstrap, migration recipes, regression checklists, and consumer-facing examples. They should route architectural questions back to this repo instead of defining IntentCall's platform contract.
Q: Why was IntentCall extracted from mcp_flutter?
A: As mcp_flutter scaled, the intent registry, wire types, sessions, and adapters became a distinct platform concern separate from the product harness (mcp_toolkit). Extracting to a standalone repo clarifies the dependency boundary: mcp_flutter is an early consumer and proof harness for IntentCall, not its architectural owner. See ADR 0010.
Q: Why is the monorepo split into so many intentcall_* packages?
A: Each package has a distinct dependency footprint and deployment target. intentcall_schema has zero Flutter dependencies; intentcall_platform requires Flutter. Keeping them separate lets backend-only consumers (CLI tools, servers) depend only on intentcall_schema + intentcall_core without pulling in Flutter.
Q: Why does intentcall_schema own the wire types rather than intentcall_core?
A: Schema types cross transport boundaries — they must be importable by every adapter and by the testing package without pulling in registry runtime logic. Keeping them in a leaf package with minimal dependencies prevents circular imports and keeps the contract stable even as the core evolves.
Q: Where do tool and resource registration types belong?
A: intentcall_core owns the transport-neutral registration vocabulary:
ToolRegistration, ToolHandler, ResourceRegistration,
ResourceTemplateRegistration, and ResourceHandler. These value objects
describe capability surfaces before any transport publishes them. Adapters such
as intentcall_mcp may re-export the types for source compatibility, but the
canonical import for non-MCP consumers is package:intentcall_core/intentcall_core.dart.
Q: Does IntentCall need a separate dynamic catalog gateway?
A: Not for the current public surface. AgentRegistry.listEntries() is the
neutral catalog snapshot for adapters because it preserves stored registry keys
and descriptors, while AgentRegistry.events is the neutral change feed.
listDescriptors() remains compatibility sugar for display-only catalog reads.
A named gateway should appear only when it owns behavior beyond listing entries
and listening to registry events.
Q: Why is intentcall_testing a separate package?
A: Test helpers have test as a dependency, which must not bleed into production packages. Isolating them in intentcall_testing lets app authors add it as a dev_dependency without the production graph growing.
Q: Why is there an intentcall_codegen package?
A: Codegen is strictly optional — many adopters will register intents by hand. Keeping it separate means the core registry has zero build_runner dependency, and users who opt in to @AgentTool get a clean code-generation surface without it affecting tree-shaking for others.
Q: Why is IntentPack direction separate from today's registry API?
A: AgentRegistry, AgentCallEntry, and RegisteredAgentIntent are the current shipped authoring/runtime model. IntentPack is the proposed portable packaging layer above them: one app-level unit that can carry entries, schemas, examples, side-effect metadata, safety policy, platform projection hints, adapter hints, and compatibility metadata. Keeping that distinction explicit lets the repo document the destination without pretending the stable public API already exists.
Q: Why are additive actions, typed entities, and indexing lifecycle not a new core runtime?
A: They are a projection layer over the existing source concepts. Actions remain registry entries. Typed entities are app-owned Dart snapshots with stable ids, display fields, and search/index fields. Indexing or donation is a lifecycle that copies those snapshots into a platform projection cache. Keeping this additive avoids turning intentcall_core into an Apple-shaped runtime while still letting Apple be the first concrete typed-entity/indexing projection. See ADR 0018.
Q: Why are there separate intentcall_apple / intentcall_android / intentcall_gemma packages instead of one intentcall_native?
A: Native surface adapters differ sharply in their platform SDKs and entitlement requirements. A single intentcall_native would force all three sets of platform SDKs into every build. Platform-specific packages let Flutter tree-shaker and pubspec platforms keys exclude irrelevant targets cleanly.
Q: Why does intentcall_session exist?
A: Sessions are runtime context for calling intents: which live app/tool endpoint is active, how it was selected, and when it was last used. That belongs beside IntentCall invocation, not inside a Flutter MCP consumer repo or a facade package. See ADR 0014.
Q: What is a session in IntentCall? A: A session is a persisted runtime attachment record, not a command catalog and not a transcript store. It keeps identity, endpoint, connection mode, active/sticky selection, and timestamps so CLI, MCP, app, and agent flows can reconnect to the same runtime without importing transport internals.
Q: What is the difference between a session manager and a broker?
A: IntentSessionManager owns lifecycle and persistence; IntentSessionExecutor resolves a session before invoking an AgentRegistry. A broker is a product-level composition of sessions, registry invocation, transport, and domain artifacts, so IntentCall exposes the reusable pieces instead of naming a separate broker facade.
Q: Why keep file-backed persistence instead of using only memory?
A: CLI/MCP debug loops often span multiple process calls, so memory-only state would lose the active endpoint between commands. StateStore, StateLockManager, and SafeFileWriter keep the existing durable behavior while still allowing tests or embedded hosts to provide temporary files.
Q: What remains adapter-specific after session extraction?
A: The IntentSessionConnector implementation remains runtime-specific. Flutter MCP keeps VM service discovery, DTD, Flutter extension calls, screenshots, widget inspection, and concrete CLI/MCP wiring; another runtime should provide its own connector and reuse the session manager.
Q: Why is dynamic registry not part of intentcall_session?
A: Dynamic registry is registry and adapter responsibility: descriptors, resource/tool snapshots, events, validation, and invocation are IntentCall core/MCP concerns. Sessions answer "which runtime am I attached to?", while registry answers "what can I call there?"
Q: Why does intentcall_session include IntentSnapshotStore?
A: Snapshot persistence is session-adjacent durable state: save a JSON runtime artifact, list it, load it, and diff it later. The store intentionally does not execute commands or know any transport; concrete hosts such as Flutter MCP build domain snapshots and pass plain JSON payloads into it.
Q: Why is IntentCall "transport-agnostic" rather than MCP-first? A: MCP is one wire format today; WebMCP, Apple App Intents, Android AppFunctions / App Actions, Windows App Actions, and protocol handlers all expose different contracts. IntentCall keeps the registry contract central, then projects it into the strongest available surface for each platform: semantic action/tool APIs where available, assistant or shortcut fulfillment where appropriate, and deep-link fallback where native support is incomplete. When platform metadata exists but native execution is not proven, generated wrappers dispatch to Dart instead of duplicating business logic.
Q: Why does intentcall_mcp use dart_mcp rather than a custom implementation?
A: Maintaining a custom MCP wire implementation would duplicate protocol work tracked upstream in the Dart ecosystem. dart_mcp is the canonical Dart MCP library; IntentCall wraps it with the registry bridge, staying thin.
Q: Why does intentcall_webmcp exist separately from intentcall_mcp?
A: WebMCP's browser-hosted tool registration model requires different lifecycle management from request/response MCP. Current WebMCP drafts use document.modelContext; compatibility with older navigator.modelContext experiments should be treated as a shim, not the primary contract.
Q: Why does IntentCall use deep links if the goal is native agent integration?
A: Deep links are the universal fallback, not the whole vision. Some platforms expose semantic agent/action registries; others only provide launch routing today. IntentCall preserves one invocation contract across both: native metadata where the platform supports it, and app-owned <scheme>://invoke/... or platform protocol handlers where it does not. Treat plain deep links as untrusted input unless they originate from generated native wrappers or an app-provided allowlist.
Q: How should platform support be described during pre-release?
A: Be explicit about tiers. WebMCP is a Dart-first in-page registration surface. Apple App Intents currently generate parameter wrappers that either dispatch to Dart after launch/wake or, with explicit nativeInline, call app-owned Swift handlers in the main app target. Apple inline runtimes can generate primitive typed App Intents returns when inlineRuntime.result is declared. Apple dartExtensionInline has a scaffold and VM-safe Dart runtime bridge, but remains experimental until a fixture extension proves target membership, FlutterEngine boot, plugin allowlisting, shared storage/IPC, limits, and live OS invocation. Android AppFunctions and Windows App Actions are roadmap native semantic targets; Android App Actions and shortcuts are assistant/shortcut fulfillment; Windows protocol activation and Linux x-scheme-handler are protocol fallback. AAIF is ecosystem alignment around agent infrastructure, not a Linux desktop intent API.
Windows App Actions URI launch and plain Windows protocol activation should not be collapsed into the same support claim. App Actions are a semantic action surface; protocol activation is fallback routing into an app-owned URI handler.
Q: Why does Apple entity querying need a native cache instead of calling Dart live? A: Apple query, shortcut, Siri, and indexing code may run while the Flutter app is cold. A projection that needs entity lookup must therefore read durable native cache data derived from Dart-owned snapshots. Dart still owns the application data and refresh/prune lifecycle; the native cache is only a projection cache used by the platform query/indexing layer.
Q: What counts as proof for donation or indexing? A: Repository tests can prove generated schemas, generated artifacts, native cache behavior, and local SDK typechecks. They do not prove live Spotlight, Siri, Shortcuts, donation, indexing, ranking, freshness, or product UX. Donation/indexing proof needs a signed consuming app installed on the target OS, or AppIntentsTesting where that API covers the scenario.
Q: Why are all packages pre-1.0 instead of 1.0.0?
A: The wire contract in intentcall_schema and the registry API in intentcall_core are still being exercised across real adapters. Publishing a 1.0.0 would imply API stability we cannot guarantee yet. Pre-release allows breaking changes to land without a semver major while the design stabilizes.
Q: Why does CI run a publish dry-run on every PR?
A: pub.dev publish validation catches issues (missing README.md, bad pubspec.yaml constraints, platform tag mismatches) that dart analyze misses. Catching them early in PR review prevents a broken pub.dev publish in a release workflow.
Q: Why is pubspec.lock committed for the workspace?
A: Workspace-level lock files ensure CI reproduces exactly the dependency graph that developers tested locally, avoiding "works on my machine" failures caused by transitive dependency updates. Per-package locks are not committed (pub.dev consumers resolve fresh).