# Getting Started

<PubVersion />

You will write a chat plugin shipping a default system prompt, an experimental plugin that A/Bs an alternative, and watch the runtime resolve to the winner without the call site ever branching.

## Packages

| Package | Description |
|---|---|
| [`plugin_kit`](https://github.com/SaadArdati/plugin_kit/tree/main/packages/plugin_kit) | Dart-only runtime: plugins, services, registry, event bus, settings, capabilities. |
| [`flutter_plugin_kit`](https://github.com/SaadArdati/plugin_kit/tree/main/packages/flutter_plugin_kit) | Flutter ergonomics: scope widgets, a `State` mixin that auto-cancels bus subscriptions, a `ChangeNotifier` adapter, and `BuildContext.watchEvent` / `readEvent` extensions. Optional. |
| [`plugin_kit_dialog`](https://github.com/SaadArdati/plugin_kit/tree/main/packages/plugin_kit_dialog) | Flutter customization UI on top of any `PluginRuntime`. Optional, but the visual proof. |

Both Flutter packages are optional. The runtime is the part you build on.

## Install

Plugin Kit is a plain Dart package. Add it to your `pubspec.yaml`.

```yaml
  dependencies:
    plugin_kit: ^PUBVER_plugin_kit
  ```

  Find the latest version on pub.dev and add it to your dependencies.

  ```yaml
  dependencies:
    plugin_kit:
      git:
        url: https://github.com/SaadArdati/plugin_kit
        ref: main
  ```

  Use this if you want to consume it directly from GitHub without waiting for a pub.dev release.

  Then run `dart pub get` (or `flutter pub get`) and you are set.

If your shell is Flutter and you want a scope widget to carry the runtime/session through the tree (plus a `State` mixin that auto-cancels bus subscriptions), add `flutter_plugin_kit` the same way. The Dart-only setup works without it; [`flutter_plugin_kit`](https://plugin-kit.saad-ardati.dev/guides/flutter-plugin-kit/) covers what it adds and when it pays for itself.

If you want the visual customization UI as well, add `plugin_kit_dialog` to your `pubspec.yaml` the same way.
The runtime works without it; [Plugin Kit Dialog](https://plugin-kit.saad-ardati.dev/guides/plugin-kit-dialog/) covers when and how to mount it.

## The whole example

Copy this into a fresh Dart file and run it. Walkthrough is below.

```dart
extractRegion(pluginLifecycleDart, 'system-prompt-taste')
```

Output:

```
You are a helpful assistant. The user is Ada. Recent activity: Opened 3 PRs, reviewed 2 designs.
```

Two plugins, one slot, one runtime, one winner. The experimental plugin won. The chat plugin's default is still registered, sitting at the lower priority. Now the parts.

## What each piece is doing

1. **Define the contract.**

   `SystemPrompt` is the abstract type that the host code asks for. Plugin Kit does NOT require interfaces, but they pay
    off the moment more than one implementation is in play.

   ```dart
   abstract class SystemPrompt {
     String build({required User user});
   }
   ```

2. **Define the implementations.**

   `DefaultSystemPrompt` and `ActivityAwareSystemPrompt` are regular Dart classes. The runtime never inspects them. How they are
    constructed, mocked, or tested is entirely up to you.

3. **Define the plugins.**

   A plugin has a unique `PluginId` and a `register` hook. The id has to be unique across the runtime; the register
    hook is where the plugin contributes things to the session.

   ```dart
   class ChatPlugin extends SessionPlugin {
     @override
     PluginId get pluginId => const PluginId('chat');
     // ...
   }
   ```

   We extend `SessionPlugin` because this feature lives inside a session rather than at the application level.
   The other scope, `GlobalPlugin`, is for behavior shared across every session. More on the split in
   [Plugins](https://plugin-kit.saad-ardati.dev/concepts/plugins/).

4. **Register competing services.**

   Both plugins register a `SystemPrompt` for the same slot, identified by `const ServiceId('system_prompt')`. `ActivityAwarePlugin`
   registers at `Priority.elevated`. `ChatPlugin` takes the default (`Priority.normal`). When the host resolves
   `SystemPrompt`, it gets the higher priority.

   The `ScopedServiceRegistry` already knows which plugin is doing the registering, so you only supply the
   `ServiceId` handle and a factory that constructs the instance.

   ```dart
   registry.registerSingleton<SystemPrompt>(
     const ServiceId('system_prompt'),
     () => ActivityAwareSystemPrompt(),
     priority: Priority.elevated,
   );
   ```

5. **Start the runtime.**

   `PluginRuntime` is the long-lived host for all your plugins. `init()` prepares the global scope. After that,
   it is ready to spawn sessions. The experimental plugin must be explicitly enabled in settings because of the
   `FeatureFlag.experimental` flag; otherwise it ships off by default.

   ```dart
   final runtime = PluginRuntime(plugins: [ChatPlugin(), ActivityAwarePlugin()])
     ..init(
       settings: const RuntimeSettings(
         plugins: {PluginId('activity_aware'): PluginConfig(enabled: true)},
       ),
     );
   ```

6. **Open a session.**

   A session is an isolated execution scope with its own registry, event bus, and context.
   You can open as many as you want, and each one runs its session plugins independently of the others.

   ```dart
   final session = await runtime.createSession();
   ```

7. **Resolve and use.**

   Ask for the service by its slot id. The session returns the winning implementation, which
   is `ActivityAwareSystemPrompt` in this case. The call site never knows there was a competition.

   ```dart
   final prompt = session.resolve<SystemPrompt>(const ServiceId('system_prompt'));
   print(prompt.build(user: User(name: 'Ada', recentActivity: '...')));
   ```

8. **Shut it down.**

   Dispose the runtime when you are done. Every attached plugin gets a chance to detach
   and clean up before the lights go out.

   ```dart
   await runtime.dispose();
   ```

## Try changing things

The library only earns its weight when things change. Three quick experiments to convince yourself that something real is happening:

- **Drop `ActivityAwarePlugin`** from the runtime's plugin list (or flip the `enabled` flag to `false`) and re-run. Output flips to the default prompt. The host code did not change.
- **Lower `ActivityAwarePlugin`'s priority below the default** (try `priority: Priority.low` or `Priority.lowest`) and re-run. Output flips back to the default. Same plugins, different winner.
- **Soft-disable `ActivityAwarePlugin` via config.** Pass `RuntimeSettings` to the runtime and mark the experimental plugin disabled.
 Both plugins still ship, both classes still exist, but the disabled one never wins:

  ```dart
extractRegion(pluginLifecycleDart, 'system-prompt-disable')
```

  Output flips to the default prompt. This is the move a settings UI makes when a user turns a feature off: same build, same code path,
  different runtime answer. Settings can also be updated live mid-session, and the runtime will reconcile the registry around the change.

That swap, where the winner changes and the call site does not, is the bet the rest of the library makes. A model selector that
follows the live winner, a settings dialog that reorders priorities, a session that loads a tenant-specific 
plugin set: all the same move at different scales.

If you want users to drive that swap themselves, [Plugin Kit Dialog](https://plugin-kit.saad-ardati.dev/guides/plugin-kit-dialog/) is a drop-in three-tab
customization UI that exposes plugin enablement, service config, and the priority registry directly. The same `RuntimeSettings` you
constructed by hand above is what the dialog produces and saves.

## What you just built

A feature has an identity (`activity_aware`), a slot it claims (`system_prompt`), and a priority that says how seriously it wants to win. The runtime owns the lifecycle. The host code asks the session, not a concrete class. The default prompt is still registered and still reachable as a fallback via `resolveAfter`; it just lost the active slot.

Everything else in Plugin Kit layers on top of this: events flying between plugins, settings flipping things live,
session-scoped state that cleans itself up, capabilities that let you discover what a service can do without building
it. Same vocabulary, applied in different directions.

## Where to go from here

[The mental model, in one sitting](https://plugin-kit.saad-ardati.dev/concepts/plugins/)
  [Richer plugin services](https://plugin-kit.saad-ardati.dev/concepts/plugin-services/)
  [Configure and reconcile at runtime](https://plugin-kit.saad-ardati.dev/guides/settings/)
  [Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/)