File size: 3,842 Bytes
488d96b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# Plugins and lifecycle

English | [δΈ­ζ–‡](index.zh.md)

This page describes the Cordis plugin model and lifecycle state machine.

## Fiber state machine

Every loaded plugin owns a **Fiber** scope with the following states:

```
PENDING β†’ LOADING β†’ ACTIVE
                 β†˜ FAILED
ACTIVE β†’ UNLOADING β†’ DISPOSED
```

| State | Meaning |
|------|------|
| PENDING | Declared, but required dependencies are not ready |
| LOADING | Dependencies are ready and `apply` is running |
| ACTIVE | The plugin is running |
| FAILED | `apply` threw an error |
| UNLOADING | The plugin is unloading and disposing resources |
| DISPOSED | The plugin is fully unloaded |

## Dependency-driven loading

A plugin with `inject` waits for every required service before loading:

```ts ignore-check
export const inject = ['tools', 'llm']

export function apply(ctx: Context) {
  // ctx.tools and ctx.llm are ready here.
}
```

If a required service disappears, for example during provider replacement, the plugin unloads automatically (ACTIVE β†’ DISPOSED) and loads again when the service returns.

## Automatic cleanup

Every registration made through `ctx` is undone when the plugin unloads:

```ts ignore-check
export function apply(ctx: Context) {
  // Event listener: removed automatically on unload.
  ctx.on('some-event', handler)

  // Custom resource: the returned disposer runs on unload.
  ctx.effect(() => {
    const connection = createConnection()
    return () => connection.close()
  })
}
```

The framework tracks and disposes all of these operations:
- `ctx.on(event, handler)` β€” event listener
- `ctx.tools.register(tool)` β€” tool registration
- `ctx.llm.registerAdapter(names, adapter)` β€” LLM adapter registration
- `ctx.effect(() => cleanup)` β€” custom resource

During unload, disposer invocation starts in reverse registration order, but multiple async disposers run concurrently and have no serial completion guarantee. Put order-dependent cleanup in one disposer returned from a single `ctx.effect()` and await its steps serially there.

## Nested contexts

`ctx.plugin()` creates a child Fiber that inherits the parent context but has an independent lifecycle:

```ts ignore-check
export function apply(ctx: Context) {
  // Register a child plugin.
  ctx.plugin(childPlugin)

  // The child has its own Fiber and unloads with its parent.
}
```

## Dispose semantics

To stop a plugin instance early:

```ts
import type { Context } from '@deepseek-ai/cordis'

declare const ctx: Context
declare function myPlugin(ctx: Context): void

const fiber = ctx.plugin(myPlugin)

// Dispose it manually later.
await fiber.dispose()
```

`dispose` guarantees:
1. All registrations owned by the plugin are removed.
2. Child plugins are recursively unloaded.
3. The returned promise resolves after all asynchronous cleanup finishes.

## Hot replacement (HMR)

With `@deepseek-ai/cordis-plugin-hmr` loaded from `cordis.yml`, editing a plugin source file triggers:

1. Unload the old plugin and clean up its registrations.
2. Load the new code.
3. Run the new `apply`.

Because plugin registrations clean themselves up, hot replacement does not retain registrations from the old instance.

## Example lifecycle

```ts ignore-check
export function apply(ctx: Context) {
  console.log('plugin loading')

  ctx.effect(() => {
    console.log('effect registered')
    return () => console.log('effect cleaned up')
  })
}
```

Loading prints:
```
plugin loading
effect registered
```

Unloading prints:
```
effect cleaned up
```

## Next steps

- [Services and dependencies](./service.md) β€” expose a capability to other plugins
- [Event system](./events.md) β€” communicate between plugins
- [Cordis tutorial](../../../cordis-tutorial/index.md) β€” the same lifecycle, services, and events built step by step against the Cordis runtime