# IntentCall — Design FAQ

*Why IntentCall is built the way it is.* For usage patterns, see [DX FAQ](/DX_FAQ).

---

## Problem & positioning

**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](decisions/0010-adopt-intentcall-product-name.md).

**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](decisions/0010-adopt-intentcall-product-name.md).

---

## Architecture

**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 `intentcall_cli` instead of `flutter-mcp-toolkit` for platform sync?**
A: Platform projection (`agent_manifest.json`, native emitters, `PlatformSync`) is framework-agnostic. `intentcall_cli` owns that contract; [mcp_flutter](https://github.com/Arenukvern/mcp_flutter) delegates to it. See [ADR 0019](decisions/0019-framework-neutral-intentcall-cli.md).

**Q: Why is `agent_manifest.json` generated rather than hand-edited?**
A: Manifest rows are a projection cache for platform emitters, not a second runtime catalog. Semantic truth lives in `AgentRegistry`; projection policy lives in `@AgentProjection` or `.intentcall/projection.yaml`. CI proves manifest freshness with `intentcall manifest export --check`.

**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](decisions/0018-additive-actions-typed-entities-indexing-lifecycle.md).

**Q: Why were `intentcall_apple` / `intentcall_android` removed, and what replaced them?**
A: Those packages were sparse-manifest generators. They were **deleted** (zero consumers, pre-1.0). Canonical projection is `intentcall_platform_sync` (`PlatformSync` / `ManifestExporter`). Runtime native code lives in federated Flutter plugins `intentcall_platform_apple` / `intentcall_platform_android` under the `intentcall_platform` umbrella ([ADR 0025](decisions/0025-platform-subset-federated-plugins.md)). On Apple, generated `AppIntent` structs compile in the app `Runner` target but import `intentcall_platform_apple` for handoff (`IntentCallNativeBridge.enqueue`) and snapshot stores — do not duplicate that logic in emitted Swift. `intentcall_gemma` remains a separate surface adapter.

**Q: Why are there two `IntentCallNativeBridge` types?**
A: Different languages, same handoff story. Dart `IntentCallNativeBridge.bindRegistry(...)` in `intentcall_platform_sync` authorizes envelopes into the registry after the app wakes. Swift `IntentCallNativeBridge.enqueue(...)` in `intentcall_platform_apple` queues native App Intent invocations and optionally opens the app-owned `protocolScheme` URL. Generated Runner Swift calls the Swift facade only.

## Runtime sessions

**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](decisions/0014-own-runtime-sessions-in-intentcall.md).

**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.

---

## Transport model

**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. A failed tool rejects the `execute` promise. See [ADR 0027](/decisions/0027-agent-usable-projection-results).

**Q: Does a tool call redraw the page or the Flutter app?**
A: No. `AgentSurfaceLink.invoke` returns an `AgentResult`. `watch` emits that result to subscribers. Neither call schedules a frame or patches the DOM. The handler updates the same state a button handler would, and the UI rebuilds through that existing path. If the handler does not touch state, the screen stays still. See [ADR 0028](/decisions/0028-surface-link-no-redraw).

**Q: How do web, CLI, and macOS share one registry?**
A: One process owns the registry and mounts `AgentInvocationHub`. The page posts to `/agent/invoke` when WebMCP is absent. A coding agent uses the Dart MCP adapter. A default WebSocket, `AgentWebSocketSurface`, carries `invoke` and `observed` frames for other processes. Apps can replace that socket with their own `AgentSurfaceLink`. The Mac app, the CLI, and the page do not discover each other until the owner serves the link and the others connect. See [ADR 0028](/decisions/0028-surface-link-no-redraw).

**Q: Why doesn't an IDE agent call App Intents or Windows App Actions directly?**
A: IDE and CLI agents speak MCP or ACP. WebMCP's consumer is an agent in the browser. App Intents' consumers are Siri, Shortcuts, and Spotlight. Windows App Actions' consumer is the Windows action runtime, and only a packaged app can register them. `dispatchMode: awaitApp` returns the Dart result to those platform callers. It does not replace `intentcall_mcp`. See [ADR 0027](/decisions/0027-agent-usable-projection-results).

**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; failed executions reject, and a missing `modelContext` is a registration report rather than a silent success. Apple App Intents for `openApp` still queue and wake. `awaitApp` waits up to 10 seconds for the running Flutter host and returns the Dart `AgentResult` in the dialog; that contract is covered by emitter tests, not by a signed Shortcuts run. 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 remain roadmap. Windows App Actions URI registration is an opt-in artifact (`windows.appActions`); protocol activation stays fallback. Unpackaged Flutter Windows builds cannot register App Actions. Agent Launchers are a different product and are not this projection. AAIF is ecosystem alignment around agent infrastructure, not a Linux desktop intent API. See [ADR 0027](/decisions/0027-agent-usable-projection-results).

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.

---

## Quality & stability

**Q: Why did the packages stay pre-1.0 for so long?**
A: Until October 2026 the wire contract in `intentcall_schema` and the registry API in `intentcall_core` were still being exercised across real adapters (oka, Flutter MCP Toolkit, WebMCP). Pre-release allowed breaking changes to land without a semver major while the design stabilized. The stable major arrived with the projection API rename (`projectEntriesToWebMcp` / `projectRegistryToWebMcp` / `WebMcpProjection`), once those adapters proved the surface.

**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).
