Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
run: dart pub get

- name: test
run: dart test packages/intentcall_schema packages/intentcall_core packages/intentcall_mcp packages/intentcall_webmcp packages/intentcall_gemma packages/intentcall_apple packages/intentcall_android packages/intentcall_platform packages/intentcall_codegen packages/intentcall_testing tool/intentcall
run: dart test packages/intentcall_schema packages/intentcall_core packages/intentcall_session packages/intentcall_mcp packages/intentcall_webmcp packages/intentcall_gemma packages/intentcall_apple packages/intentcall_android packages/intentcall_platform packages/intentcall_codegen packages/intentcall_testing tool/intentcall

- name: analyze
run: dart analyze .
Expand Down
7 changes: 4 additions & 3 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ checks only as historical/diagnostic guidance.

1. `intentcall_schema`
2. `intentcall_core`
3. `intentcall_mcp`, `intentcall_webmcp`, `intentcall_gemma`, `intentcall_apple`, `intentcall_android`, `intentcall_codegen`
4. `intentcall_platform` (Flutter plugin — may need `flutter pub publish`)
5. `intentcall_testing`
3. `intentcall_session`
4. `intentcall_mcp`, `intentcall_webmcp`, `intentcall_gemma`, `intentcall_apple`, `intentcall_android`, `intentcall_codegen`
5. `intentcall_platform` (Flutter plugin — may need `flutter pub publish`)
6. `intentcall_testing`

## Commands

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ GitHub: [Arenukvern/intentcall](https://github.com/Arenukvern/intentcall)
|---------|------|
| `intentcall_schema` | Wire types, validation, `AgentResult` |
| `intentcall_core` | Registry, runtime, `AgentCallEntry` |
| `intentcall_session` | Runtime session lifecycle, persisted session state, and registry execution inside a session |
| `intentcall_mcp` | MCP publish adapter (`dart_mcp`) |
| `intentcall_webmcp` | WebMCP hot-sync adapter |
| `intentcall_platform` | Native/web emitters, protocol fallback artifacts, and Flutter plugin |
Expand Down
23 changes: 23 additions & 0 deletions docs/DESIGN_FAQ.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ A: `AgentRegistry`, `AgentCallEntry`, and `RegisteredAgentIntent` are the curren
**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.

## 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
Expand Down
128 changes: 121 additions & 7 deletions docs/DX_FAQ.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,109 @@ Wire types (`AgentResult`, validation helpers, resource input schemas) live in `

---

## 🧭 Runtime sessions

**Q: How do I add reusable runtime session lifecycle to a CLI or tool?**

Use `intentcall_session` with a connector that knows how to attach to your runtime. The connector owns target discovery and transport details; `IntentSessionManager` owns persisted session state.

```dart
import 'package:intentcall_session/intentcall_session.dart';

final sessions = IntentSessionManager(
connector: myConnector,
stateStore: StateStore(path: '.intentcall/session_state.json'),
);

await sessions.load();
final start = await sessions.startSession(
const IntentSessionStartRequest(
mode: IntentSessionConnectionMode.uri,
uri: 'ws://127.0.0.1:8181/ws',
sessionId: 'debug',
),
);
```

**Q: What does my connector need to implement?**

Implement `IntentSessionConnector`. Return a stable `activeEndpointDisplay` after `connect`; throw `IntentSessionConnectionException` with `multipleTargets`, `targetNotFound`, `noTargets`, `invalidUri`, or `invalidTargetId` when selection fails.

```dart
final class MyConnector implements IntentSessionConnector {
@override
String? activeEndpointDisplay;

@override
Map<String, Object?> get lastSelectionDiagnostics => const {};

@override
Future<Map<String, Object?>> connect({
IntentSessionConnectionMode mode = IntentSessionConnectionMode.auto,
String? targetId,
String? uri,
String? host,
int? port,
bool forceReconnect = false,
}) async {
activeEndpointDisplay = uri ?? 'runtime://current';
return {'connected': true, 'reusedConnection': false};
}

@override
Future<void> disconnect() async {
activeEndpointDisplay = null;
}
}
```

**Q: How do I invoke an intent inside the active session?**

Use `IntentSessionExecutor`. It attaches first, invokes the registry, and marks the session used after successful calls.

```dart
import 'package:intentcall_core/intentcall_core.dart';
import 'package:intentcall_session/intentcall_session.dart';

final executor = IntentSessionExecutor(
sessions: sessions,
registry: registry,
);

final result = await executor.invoke(
qualifiedName: 'debug_select',
arguments: const {'id': 'node-7'},
sessionId: 'debug',
);
```

**Q: Should I build a broker package on top of this?**

Usually no. Compose `IntentSessionManager`, `IntentSessionExecutor`, `AgentRegistry`, and your transport adapter directly; introduce a named broker only when it owns real product behavior such as routing policy, artifact storage, or multi-runtime coordination.

**Q: How do I persist and diff JSON runtime snapshots?**

Use `IntentSnapshotStore` for storage only. Build the snapshot payload in your host, then let the store handle safe writes, listing, loading, and structural diffs.

```dart
final snapshots = IntentSnapshotStore(
snapshotsDir: '.intentcall/snapshots',
);

await snapshots.saveSnapshot(
id: 'before',
snapshot: const {
'id': 'before',
'createdAt': '2026-06-22T00:00:00.000Z',
'result': {'selected': 'node-7'},
},
);

final diff = await snapshots.diffSnapshots(fromId: 'before', toId: 'after');
```

---

## 🧭 IntentPack direction

**Q: Should I use `IntentPack` today?**
Expand Down Expand Up @@ -142,6 +245,17 @@ No. You can register `AgentCallEntry` objects manually. Codegen is a convenience

Add `intentcall_testing` as a `dev_dependency` and use the provided `AgentCallContractTest` mixin. See the test suite in `packages/intentcall_mcp/test/` for a concrete example.

**Q: How do I test session lifecycle without Flutter or MCP?**

Use an in-memory fake connector and a temporary `StateStore` path. This proves persistence, lock handling, and session executor behavior without importing adapter internals.

```dart
final manager = IntentSessionManager(
connector: FakeConnector(endpoint: 'runtime://test'),
stateStore: StateStore(path: '${tempDir.path}/state.json'),
);
```

**Q: How do I run integration tests against the mcp_flutter sibling repo?**

```bash
Expand All @@ -158,9 +272,10 @@ make check-intentcall-integration

1. `intentcall_schema`
2. `intentcall_core`
3. `intentcall_mcp`, `intentcall_webmcp`, `intentcall_gemma`, `intentcall_apple`, `intentcall_android`, `intentcall_codegen` (parallel)
4. `intentcall_platform`
5. `intentcall_testing`
3. `intentcall_session`
4. `intentcall_mcp`, `intentcall_webmcp`, `intentcall_gemma`, `intentcall_apple`, `intentcall_android`, `intentcall_codegen` (parallel)
5. `intentcall_platform`
6. `intentcall_testing`

**Q: How do I publish?**

Expand Down Expand Up @@ -202,10 +317,9 @@ dependency_overrides:
# … etc
```

After a GitHub rename to `intentcall`, update the path to
`../intentcall/packages/…` or use `INTENTCALL_ROOT` env var for local source
validation:
If your checkout uses a different local folder name, update the path or use
`INTENTCALL_ROOT` for local source validation:

```bash
INTENTCALL_ROOT=~/mcp/intentcall make check-intentcall-integration
INTENTCALL_ROOT=~/mcp/agentkit make check-intentcall-integration
```
71 changes: 71 additions & 0 deletions docs/decisions/0014-own-runtime-sessions-in-intentcall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
status: accepted
date: 2026-06-22
decision-makers: IntentCall maintainers
consulted:
informed:
---

# Own runtime sessions in IntentCall

## Context and Problem Statement

IntentCall already owns the reusable callable surface: registry, descriptors,
invocation, results, artifacts, and registry events. `mcp_flutter` previously
grew CLI session state around Flutter VM connections, then a temporary broker
extraction risked adding another command and dynamic-registry layer beside
IntentCall.

Downstream tools need the same session mechanics without importing Flutter MCP
server internals: start or attach to a live runtime, persist the selected
endpoint, invoke registered intents, and keep file-backed session state durable.

## Decision Drivers

* **Single command model** - IntentCall invocation is the command envelope.
* **No facade packages** - public packages must own behavior, not re-export it.
* **Hard-cut clarity** - pre-release consumers should update imports instead of
carrying stale compatibility exports.
* **Runtime neutrality** - sessions should work for CLI, MCP, apps, and tools
without pulling in Flutter VM or MCP server dependencies.
* **Persistence continuity** - existing file-backed session behavior remains
the default.

## Considered Options

* **Keep a broker package in `mcp_flutter`** - rejected because it would sit
beside IntentCall and duplicate registry/invocation ownership.
* **Rename broker to a toolkit session package** - rejected because reusable
session semantics belong with the IntentCall runtime, not a consumer repo.
* **Move sessions into IntentCall** - chosen because sessions are runtime
context for IntentCall invocation.

## Decision Outcome

Create `intentcall_session` as the owner of runtime session lifecycle and
persistence. It provides file-backed session state, state locking, safe writes,
session start/attach/end operations, and an `AgentRegistry` session executor.

The package does not define a dynamic registry, command catalog, artifact model,
transport, Flutter VM connector, MCP server, or visual debugger. Those remain in
`intentcall_core`, `intentcall_schema`, adapter packages, or concrete consumer
repos.

`mcp_flutter` consumes `intentcall_session` directly. The temporary broker
package and compatibility re-export shims are removed.

### Consequences

* Good, because downstreams use the same session behavior without Flutter MCP
server internals.
* Good, because IntentCall remains the single owner of dynamic registry and
invocation semantics.
* Neutral, because Flutter MCP still needs a concrete connector adapter for VM
service targets.
* Bad, because pre-release consumers must update imports; accepted because stale
compatibility layers would obscure the real owner.

## Links

* [NORTH_STAR.md](../NORTH_STAR.mdx)
* [DESIGN_FAQ.md](../DESIGN_FAQ.mdx)
3 changes: 2 additions & 1 deletion docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Architecture Decision Records (ADRs) for IntentCall.
Format: [MADR](https://adr.github.io/madr/) — see any existing ADR for the template.

Next ADR number: **0014**
Next ADR number: **0015**

---

Expand All @@ -15,6 +15,7 @@ Next ADR number: **0014**
| [0011](0011-agent-skills-discoverability-for-intentcall.md) | accepted | Agent Skills Discoverability and Custom Skills for IntentCall | 2026-06-02 |
| [0012](0012-adopt-platform-support-tiers.md) | accepted | Adopt platform support tiers for IntentCall | 2026-06-10 |
| [0013](0013-delete-implemented-plans-after-durable-extraction.md) | accepted | Delete implemented plans after durable extraction | 2026-06-10 |
| [0014](0014-own-runtime-sessions-in-intentcall.md) | accepted | Own runtime sessions in IntentCall | 2026-06-22 |

---

Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default:

# Run tests for all packages in the workspace
test:
dart test packages/intentcall_schema packages/intentcall_core packages/intentcall_mcp packages/intentcall_webmcp packages/intentcall_gemma packages/intentcall_apple packages/intentcall_android packages/intentcall_platform packages/intentcall_codegen packages/intentcall_testing tool/intentcall
dart test packages/intentcall_schema packages/intentcall_core packages/intentcall_session packages/intentcall_mcp packages/intentcall_webmcp packages/intentcall_gemma packages/intentcall_apple packages/intentcall_android packages/intentcall_platform packages/intentcall_codegen packages/intentcall_testing tool/intentcall

# Analyze the Dart code in the workspace
analyze:
Expand All @@ -33,7 +33,7 @@ publish-preflight-first:
publish-execute:
dart run tool/intentcall/bin/intentcall.dart publish-all --execute

# Check for path dependencies pointing to intentcall/packages
# Check for local IntentCall path dependencies in publishable packages
check-path-deps:
dart run tool/intentcall/bin/intentcall.dart check-path-deps

Expand Down
4 changes: 4 additions & 0 deletions packages/intentcall_core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ await runtime.start();

- `AgentResult.envelope` / `resourceEnvelope` (`intentcall_schema`)
- `AgentWireArgs` for string-key maps
- Tool/resource registration contracts for hosts that expose capability
surfaces without depending on a concrete transport adapter
- `AgentClientInstall.once` in `mcp_toolkit` for lazy registration

## Migration helpers
Expand All @@ -59,6 +61,8 @@ Use this import for `MigrateAgentEntriesMigrator`,
## Related packages

- `intentcall_schema` — results, validation, wire args
- `intentcall_session` — reusable runtime session state, lifecycle, and JSON
snapshot persistence
- `intentcall_mcp` — MCP bridge, publish adapter, resource mapper
- `intentcall_webmcp` — WebMCP `modelContext` publish adapter
- `intentcall_gemma` — on-device Gemma function-calling adapter
Expand Down
3 changes: 3 additions & 0 deletions packages/intentcall_core/lib/intentcall_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export 'src/intent/registered_agent_intent.dart';
export 'src/module/agent_module.dart';
export 'src/module/agent_module_from_entries.dart';
export 'src/naming/qualified_name.dart';
export 'src/registration/resource_registration.dart';
export 'src/registration/resource_template_registration.dart';
export 'src/registration/tool_registration.dart';
export 'src/registry/agent_registry.dart';
export 'src/registry/agent_registry_errors.dart';
export 'src/registry/in_memory_agent_registry.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ typedef _AgentCallEntryValue = ({

extension type const AgentCallEntry._(
MapEntry<String, _AgentCallEntryValue> _entry
)
implements MapEntry<String, _AgentCallEntryValue> {
) implements MapEntry<String, _AgentCallEntryValue> {
factory AgentCallEntry.tool({
required final String namespace,
required final String name,
Expand Down Expand Up @@ -95,10 +94,7 @@ extension type const AgentCallEntry._(
);
registration.validate(coerced);
return registration.execute(
AgentInvocation(
descriptor: registration.descriptor,
arguments: coerced,
),
AgentInvocation(descriptor: registration.descriptor, arguments: coerced),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import 'package:intentcall_schema/intentcall_schema.dart';
import 'agent_intent_descriptor.dart';
import 'agent_invocation.dart';

typedef AgentExecutor = Future<AgentResult> Function(AgentInvocation invocation);
typedef AgentExecutor =
Future<AgentResult> Function(AgentInvocation invocation);
typedef AgentValidator = void Function(AgentArguments arguments);

final class RegisteredAgentIntent {
Expand Down
Loading
Loading