dsh-plugin-dev-kb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/kb/INDEX.md +210 -0
  5. package/kb/README.md +69 -0
  6. package/kb/extra/AGENTS.md +75 -0
  7. package/kb/extra/api-gateway.md +164 -0
  8. package/kb/extra/api-gateway.zh.md +164 -0
  9. package/kb/extra/cookbook/adding-a-vendored-package.md +59 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +59 -0
  11. package/kb/extra/cookbook/maintaining-dsh-code-review.md +64 -0
  12. package/kb/extra/cookbook/maintaining-dsh-code-review.zh.md +64 -0
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
  15. package/kb/extra/defensive-patterns.md +33 -0
  16. package/kb/extra/defensive-patterns.zh.md +33 -0
  17. package/kb/extra/development.md +171 -0
  18. package/kb/extra/development.zh.md +171 -0
  19. package/kb/extra/event-producer-consumer.md +76 -0
  20. package/kb/extra/event-producer-consumer.zh.md +78 -0
  21. package/kb/extra/glossary.md +45 -0
  22. package/kb/extra/glossary.zh.md +45 -0
  23. package/kb/extra/graph-atlas.md +24 -0
  24. package/kb/extra/graph-atlas.zh.md +26 -0
  25. package/kb/extra/i18n/README.md +60 -0
  26. package/kb/extra/i18n/README.zh.md +60 -0
  27. package/kb/extra/i18n/style-samples.md +87 -0
  28. package/kb/extra/i18n/terminology.md +214 -0
  29. package/kb/extra/i18n/translation-prompt.md +263 -0
  30. package/kb/extra/i18n/translation-rules.md +69 -0
  31. package/kb/extra/i18n/translation-rules.zh.md +69 -0
  32. package/kb/extra/module-graph.md +1641 -0
  33. package/kb/extra/module-graph.zh.md +1643 -0
  34. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +113 -0
  35. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
  36. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
  37. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
  38. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
  39. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
  40. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
  41. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
  42. package/kb/extra/postmortem/README.md +18 -0
  43. package/kb/extra/postmortem/README.zh.md +18 -0
  44. package/kb/extra/rescope.md +53 -0
  45. package/kb/extra/rescope.zh.md +53 -0
  46. package/kb/extra/subsystems/attachment.md +125 -0
  47. package/kb/extra/subsystems/attachment.zh.md +125 -0
  48. package/kb/extra/subsystems/extensions.md +364 -0
  49. package/kb/extra/subsystems/extensions.zh.md +364 -0
  50. package/kb/extra/subsystems/feedback.md +266 -0
  51. package/kb/extra/subsystems/feedback.zh.md +266 -0
  52. package/kb/extra/testing.md +49 -0
  53. package/kb/extra/testing.zh.md +49 -0
  54. package/kb/extra/web-styling.md +25 -0
  55. package/kb/extra/web-styling.zh.md +25 -0
  56. package/kb/meta/search-index.json +1328 -0
  57. package/kb/meta/site-pages.txt +168 -0
  58. package/kb/meta/source.json +13 -0
  59. package/kb/meta/topics.md +75 -0
  60. package/kb/site/develop/basic/config.md +108 -0
  61. package/kb/site/develop/basic/index.md +146 -0
  62. package/kb/site/develop/basic/publish.md +185 -0
  63. package/kb/site/develop/basic/tool.md +54 -0
  64. package/kb/site/develop/cordis-tutorial/01-first-plugin.md +95 -0
  65. package/kb/site/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  66. package/kb/site/develop/cordis-tutorial/03-services.md +98 -0
  67. package/kb/site/develop/cordis-tutorial/04-events.md +144 -0
  68. package/kb/site/develop/cordis-tutorial/05-config.md +84 -0
  69. package/kb/site/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  70. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  71. package/kb/site/develop/cordis-tutorial/index.md +62 -0
  72. package/kb/site/develop/framework/events.md +145 -0
  73. package/kb/site/develop/framework/index.md +139 -0
  74. package/kb/site/develop/framework/service.md +152 -0
  75. package/kb/site/develop/practice/index.md +157 -0
  76. package/kb/site/develop/practice/llm-adapter.md +190 -0
  77. package/kb/site/en/develop/basic/config.md +108 -0
  78. package/kb/site/en/develop/basic/index.md +146 -0
  79. package/kb/site/en/develop/basic/publish.md +185 -0
  80. package/kb/site/en/develop/basic/tool.md +54 -0
  81. package/kb/site/en/develop/cordis-tutorial/01-first-plugin.md +95 -0
  82. package/kb/site/en/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  83. package/kb/site/en/develop/cordis-tutorial/03-services.md +98 -0
  84. package/kb/site/en/develop/cordis-tutorial/04-events.md +144 -0
  85. package/kb/site/en/develop/cordis-tutorial/05-config.md +84 -0
  86. package/kb/site/en/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  87. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  88. package/kb/site/en/develop/cordis-tutorial/index.md +60 -0
  89. package/kb/site/en/develop/framework/events.md +145 -0
  90. package/kb/site/en/develop/framework/index.md +139 -0
  91. package/kb/site/en/develop/framework/service.md +150 -0
  92. package/kb/site/en/develop/practice/index.md +157 -0
  93. package/kb/site/en/develop/practice/llm-adapter.md +190 -0
  94. package/kb/site/en/guide/providers-custom-form.png +0 -0
  95. package/kb/site/en/guide/providers-models-page.png +0 -0
  96. package/kb/site/en/guide/providers.md +100 -0
  97. package/kb/site/en/guide/python-sdk.md +106 -0
  98. package/kb/site/en/guide/quickstart.md +32 -0
  99. package/kb/site/en/index.md +8 -0
  100. package/kb/site/en/reference/agent-lifecycle.md +86 -0
  101. package/kb/site/en/reference/capability-seams.md +475 -0
  102. package/kb/site/en/reference/config-catalog.md +3155 -0
  103. package/kb/site/en/reference/cookbook/adding-a-conversation-node.md +235 -0
  104. package/kb/site/en/reference/cookbook/adding-a-package.md +120 -0
  105. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +102 -0
  106. package/kb/site/en/reference/cookbook/adding-a-tool.md +96 -0
  107. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +45 -0
  108. package/kb/site/en/reference/cookbook/extension-cookbook.md +131 -0
  109. package/kb/site/en/reference/cordis-api/context.md +368 -0
  110. package/kb/site/en/reference/cordis-api/events.md +211 -0
  111. package/kb/site/en/reference/cordis-api/fiber.md +379 -0
  112. package/kb/site/en/reference/cordis-api/inherited.md +43 -0
  113. package/kb/site/en/reference/cordis-api/registry.md +156 -0
  114. package/kb/site/en/reference/cordis-api/service.md +106 -0
  115. package/kb/site/en/reference/cordis-primer.md +46 -0
  116. package/kb/site/en/reference/index.md +131 -0
  117. package/kb/site/en/reference/persistence-catalog.md +949 -0
  118. package/kb/site/en/reference/subsystems/approval.md +173 -0
  119. package/kb/site/en/reference/subsystems/client-modules.md +121 -0
  120. package/kb/site/en/reference/subsystems/code-runtime.md +194 -0
  121. package/kb/site/en/reference/subsystems/commands.md +190 -0
  122. package/kb/site/en/reference/subsystems/compaction.md +241 -0
  123. package/kb/site/en/reference/subsystems/core.md +1073 -0
  124. package/kb/site/en/reference/subsystems/credentials.md +136 -0
  125. package/kb/site/en/reference/subsystems/filesystem.md +498 -0
  126. package/kb/site/en/reference/subsystems/goal.md +280 -0
  127. package/kb/site/en/reference/subsystems/index.md +58 -0
  128. package/kb/site/en/reference/subsystems/invariants.md +91 -0
  129. package/kb/site/en/reference/subsystems/jobs.md +293 -0
  130. package/kb/site/en/reference/subsystems/llm-streaming.md +920 -0
  131. package/kb/site/en/reference/subsystems/lsp.md +205 -0
  132. package/kb/site/en/reference/subsystems/permission-presets.md +134 -0
  133. package/kb/site/en/reference/subsystems/persistence.md +388 -0
  134. package/kb/site/en/reference/subsystems/plan.md +90 -0
  135. package/kb/site/en/reference/subsystems/sandbox.md +221 -0
  136. package/kb/site/en/reference/subsystems/schedule.md +189 -0
  137. package/kb/site/en/reference/subsystems/scope.md +62 -0
  138. package/kb/site/en/reference/subsystems/session-projection.md +265 -0
  139. package/kb/site/en/reference/subsystems/session-query.md +498 -0
  140. package/kb/site/en/reference/subsystems/session-reference.md +111 -0
  141. package/kb/site/en/reference/subsystems/session-telemetry.md +197 -0
  142. package/kb/site/en/reference/subsystems/session-title.md +207 -0
  143. package/kb/site/en/reference/subsystems/session.md +852 -0
  144. package/kb/site/en/reference/subsystems/settings.md +313 -0
  145. package/kb/site/en/reference/subsystems/shell.md +306 -0
  146. package/kb/site/en/reference/subsystems/skills.md +334 -0
  147. package/kb/site/en/reference/subsystems/spill.md +120 -0
  148. package/kb/site/en/reference/subsystems/storage.md +232 -0
  149. package/kb/site/en/reference/subsystems/subagent.md +737 -0
  150. package/kb/site/en/reference/subsystems/subprocess.md +327 -0
  151. package/kb/site/en/reference/subsystems/system-prompt.md +210 -0
  152. package/kb/site/en/reference/subsystems/terminal.md +187 -0
  153. package/kb/site/en/reference/subsystems/token-meter.md +93 -0
  154. package/kb/site/en/reference/subsystems/tools.md +723 -0
  155. package/kb/site/en/reference/subsystems/typert.md +339 -0
  156. package/kb/site/en/reference/subsystems/user-questions.md +181 -0
  157. package/kb/site/en/reference/subsystems/web-server.md +111 -0
  158. package/kb/site/en/reference/subsystems/web.md +202 -0
  159. package/kb/site/en/reference/subsystems/workflow.md +281 -0
  160. package/kb/site/en/reference/subsystems/workspace.md +231 -0
  161. package/kb/site/en/reference/tool-catalog.md +1877 -0
  162. package/kb/site/en/reference/tool-execution-pipeline.md +66 -0
  163. package/kb/site/guide/providers-custom-form.zh.png +0 -0
  164. package/kb/site/guide/providers-models-page.zh.png +0 -0
  165. package/kb/site/guide/providers.md +100 -0
  166. package/kb/site/guide/python-sdk.md +106 -0
  167. package/kb/site/guide/quickstart.md +32 -0
  168. package/kb/site/index.md +8 -0
  169. package/kb/site/reference/agent-lifecycle.md +86 -0
  170. package/kb/site/reference/capability-seams.md +475 -0
  171. package/kb/site/reference/config-catalog.md +3154 -0
  172. package/kb/site/reference/cookbook/adding-a-conversation-node.md +235 -0
  173. package/kb/site/reference/cookbook/adding-a-package.md +120 -0
  174. package/kb/site/reference/cookbook/adding-a-settings-card.md +102 -0
  175. package/kb/site/reference/cookbook/adding-a-tool.md +98 -0
  176. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +45 -0
  177. package/kb/site/reference/cookbook/extension-cookbook.md +133 -0
  178. package/kb/site/reference/cordis-api/context.md +368 -0
  179. package/kb/site/reference/cordis-api/events.md +211 -0
  180. package/kb/site/reference/cordis-api/fiber.md +379 -0
  181. package/kb/site/reference/cordis-api/inherited.md +43 -0
  182. package/kb/site/reference/cordis-api/registry.md +156 -0
  183. package/kb/site/reference/cordis-api/service.md +106 -0
  184. package/kb/site/reference/cordis-primer.md +52 -0
  185. package/kb/site/reference/index.md +135 -0
  186. package/kb/site/reference/persistence-catalog.md +949 -0
  187. package/kb/site/reference/subsystems/approval.md +173 -0
  188. package/kb/site/reference/subsystems/client-modules.md +121 -0
  189. package/kb/site/reference/subsystems/code-runtime.md +194 -0
  190. package/kb/site/reference/subsystems/commands.md +190 -0
  191. package/kb/site/reference/subsystems/compaction.md +241 -0
  192. package/kb/site/reference/subsystems/core.md +1081 -0
  193. package/kb/site/reference/subsystems/credentials.md +136 -0
  194. package/kb/site/reference/subsystems/filesystem.md +498 -0
  195. package/kb/site/reference/subsystems/goal.md +280 -0
  196. package/kb/site/reference/subsystems/index.md +58 -0
  197. package/kb/site/reference/subsystems/invariants.md +91 -0
  198. package/kb/site/reference/subsystems/jobs.md +293 -0
  199. package/kb/site/reference/subsystems/llm-streaming.md +926 -0
  200. package/kb/site/reference/subsystems/lsp.md +205 -0
  201. package/kb/site/reference/subsystems/permission-presets.md +134 -0
  202. package/kb/site/reference/subsystems/persistence.md +388 -0
  203. package/kb/site/reference/subsystems/plan.md +90 -0
  204. package/kb/site/reference/subsystems/sandbox.md +221 -0
  205. package/kb/site/reference/subsystems/schedule.md +189 -0
  206. package/kb/site/reference/subsystems/scope.md +62 -0
  207. package/kb/site/reference/subsystems/session-projection.md +265 -0
  208. package/kb/site/reference/subsystems/session-query.md +498 -0
  209. package/kb/site/reference/subsystems/session-reference.md +111 -0
  210. package/kb/site/reference/subsystems/session-telemetry.md +197 -0
  211. package/kb/site/reference/subsystems/session-title.md +207 -0
  212. package/kb/site/reference/subsystems/session.md +854 -0
  213. package/kb/site/reference/subsystems/settings.md +313 -0
  214. package/kb/site/reference/subsystems/shell.md +306 -0
  215. package/kb/site/reference/subsystems/skills.md +334 -0
  216. package/kb/site/reference/subsystems/spill.md +120 -0
  217. package/kb/site/reference/subsystems/storage.md +232 -0
  218. package/kb/site/reference/subsystems/subagent.md +739 -0
  219. package/kb/site/reference/subsystems/subprocess.md +327 -0
  220. package/kb/site/reference/subsystems/system-prompt.md +210 -0
  221. package/kb/site/reference/subsystems/terminal.md +187 -0
  222. package/kb/site/reference/subsystems/token-meter.md +93 -0
  223. package/kb/site/reference/subsystems/tools.md +723 -0
  224. package/kb/site/reference/subsystems/typert.md +339 -0
  225. package/kb/site/reference/subsystems/user-questions.md +181 -0
  226. package/kb/site/reference/subsystems/web-server.md +111 -0
  227. package/kb/site/reference/subsystems/web.md +202 -0
  228. package/kb/site/reference/subsystems/workflow.md +281 -0
  229. package/kb/site/reference/subsystems/workspace.md +231 -0
  230. package/kb/site/reference/tool-catalog.md +1880 -0
  231. package/kb/site/reference/tool-execution-pipeline.md +66 -0
  232. package/package.json +40 -0
  233. package/scripts/rebuild-index.mjs +88 -0
  234. package/skills/dsh-plugin-dev-kb.md +66 -0
@@ -0,0 +1,139 @@
1
+ ---
2
+ editSource: "docs/user/develop/framework/index.md"
3
+ ---
4
+
5
+ # Plugins and lifecycle
6
+
7
+ This page describes the Cordis plugin model and lifecycle state machine.
8
+
9
+ ## Fiber state machine
10
+
11
+ Every loaded plugin owns a **Fiber** scope with the following states:
12
+
13
+ ```
14
+ PENDING → LOADING → ACTIVE
15
+ ↘ FAILED
16
+ ACTIVE → UNLOADING → DISPOSED
17
+ ```
18
+
19
+ | State | Meaning |
20
+ |------|------|
21
+ | PENDING | Declared, but required dependencies are not ready |
22
+ | LOADING | Dependencies are ready and `apply` is running |
23
+ | ACTIVE | The plugin is running |
24
+ | FAILED | `apply` threw an error |
25
+ | UNLOADING | The plugin is unloading and disposing resources |
26
+ | DISPOSED | The plugin is fully unloaded |
27
+
28
+ ## Dependency-driven loading
29
+
30
+ A plugin with `inject` waits for every required service before loading:
31
+
32
+ ```ts ignore-check
33
+ export const inject = ['tools', 'llm']
34
+
35
+ export function apply(ctx: Context) {
36
+ // ctx.tools and ctx.llm are ready here.
37
+ }
38
+ ```
39
+
40
+ If a required service disappears, for example during provider replacement, the plugin unloads automatically (ACTIVE → DISPOSED) and loads again when the service returns.
41
+
42
+ ## Automatic cleanup
43
+
44
+ Every registration made through `ctx` is undone when the plugin unloads:
45
+
46
+ ```ts ignore-check
47
+ export function apply(ctx: Context) {
48
+ // Event listener: removed automatically on unload.
49
+ ctx.on('some-event', handler)
50
+
51
+ // Custom resource: the returned disposer runs on unload.
52
+ ctx.effect(() => {
53
+ const connection = createConnection()
54
+ return () => connection.close()
55
+ })
56
+ }
57
+ ```
58
+
59
+ The framework tracks and disposes all of these operations:
60
+ - `ctx.on(event, handler)` — event listener
61
+ - `ctx.tools.register(tool)` — tool registration
62
+ - `ctx.llm.registerAdapter(names, adapter)` — LLM adapter registration
63
+ - `ctx.effect(() => cleanup)` — custom resource
64
+
65
+ 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.
66
+
67
+ ## Nested contexts
68
+
69
+ `ctx.plugin()` creates a child Fiber that inherits the parent context but has an independent lifecycle:
70
+
71
+ ```ts ignore-check
72
+ export function apply(ctx: Context) {
73
+ // Register a child plugin.
74
+ ctx.plugin(childPlugin)
75
+
76
+ // The child has its own Fiber and unloads with its parent.
77
+ }
78
+ ```
79
+
80
+ ## Dispose semantics
81
+
82
+ To stop a plugin instance early:
83
+
84
+ ```ts
85
+ import type { Context } from '@deepseek-ai/cordis'
86
+
87
+ declare const ctx: Context
88
+ declare function myPlugin(ctx: Context): void
89
+
90
+ const fiber = ctx.plugin(myPlugin)
91
+
92
+ // Dispose it manually later.
93
+ await fiber.dispose()
94
+ ```
95
+
96
+ `dispose` guarantees:
97
+ 1. All registrations owned by the plugin are removed.
98
+ 2. Child plugins are recursively unloaded.
99
+ 3. The returned promise resolves after all asynchronous cleanup finishes.
100
+
101
+ ## Hot replacement (HMR)
102
+
103
+ With `@deepseek-ai/cordis-plugin-hmr` loaded from `cordis.yml`, editing a plugin source file triggers:
104
+
105
+ 1. Unload the old plugin and clean up its registrations.
106
+ 2. Load the new code.
107
+ 3. Run the new `apply`.
108
+
109
+ Because plugin registrations clean themselves up, hot replacement does not retain registrations from the old instance.
110
+
111
+ ## Example lifecycle
112
+
113
+ ```ts ignore-check
114
+ export function apply(ctx: Context) {
115
+ console.log('plugin loading')
116
+
117
+ ctx.effect(() => {
118
+ console.log('effect registered')
119
+ return () => console.log('effect cleaned up')
120
+ })
121
+ }
122
+ ```
123
+
124
+ Loading prints:
125
+ ```
126
+ plugin loading
127
+ effect registered
128
+ ```
129
+
130
+ Unloading prints:
131
+ ```
132
+ effect cleaned up
133
+ ```
134
+
135
+ ## Next steps
136
+
137
+ - [Services and dependencies](./service.md) — expose a capability to other plugins
138
+ - [Event system](./events.md) — communicate between plugins
139
+ - [Cordis tutorial](../cordis-tutorial/index.md) — the same lifecycle, services, and events built step by step against the Cordis runtime
@@ -0,0 +1,150 @@
1
+ ---
2
+ editSource: "docs/user/develop/framework/service.md"
3
+ ---
4
+
5
+ # Services and dependencies
6
+
7
+ A service is a capability one plugin exposes to other plugins. `inject` declares the services a plugin requires.
8
+
9
+ ## What is a service?
10
+
11
+ In Harness, `tools`, `llm`, and `agents` are services. Each is a named capability mounted on `ctx`:
12
+
13
+ ```ts ignore-check
14
+ ctx.tools // ToolRuntime service
15
+ ctx.llm // LLM service
16
+ ctx.agents // Agent service
17
+ ```
18
+
19
+ Any plugin can provide a service for other plugins to consume.
20
+
21
+ ## Consume a service
22
+
23
+ Declare `inject` to use an existing service:
24
+
25
+ ```ts ignore-check
26
+ export const inject = ['tools']
27
+
28
+ export function apply(ctx: Context) {
29
+ // ctx.tools exists and is ready here.
30
+ ctx.tools.register(/* ... */)
31
+ }
32
+ ```
33
+
34
+ When `apply` runs, every service declared by `inject` is ready. If a service is not ready, the plugin waits instead of running.
35
+
36
+ ## Provide a service
37
+
38
+ ### Extend Service
39
+
40
+ ```ts
41
+ import { Service, type Context } from '@deepseek-ai/cordis'
42
+
43
+ export default class MetricsService extends Service {
44
+ static inject = ['llm'] // A service may depend on other services.
45
+
46
+ constructor(ctx: Context) {
47
+ super(ctx, 'metrics') // 'metrics' is the service name.
48
+ }
49
+
50
+ // Public service method.
51
+ record(event: string, value: number) {
52
+ // ...
53
+ }
54
+ }
55
+ ```
56
+
57
+ After loading this plugin, consumers access the service as `ctx.metrics`:
58
+
59
+ ```ts ignore-check
60
+ export const inject = ['metrics']
61
+
62
+ export function apply(ctx: Context) {
63
+ ctx.metrics.record('tool_call', 1)
64
+ }
65
+ ```
66
+
67
+ ### Declare its type
68
+
69
+ Use TypeScript declaration merging to type `ctx.metrics`:
70
+
71
+ ```ts
72
+ import { Service, type Context } from '@deepseek-ai/cordis'
73
+
74
+ declare module '@deepseek-ai/cordis' {
75
+ interface Context {
76
+ metrics: MetricsService
77
+ }
78
+ }
79
+
80
+ export default class MetricsService extends Service {
81
+ constructor(ctx: Context) {
82
+ super(ctx, 'metrics')
83
+ }
84
+
85
+ record(event: string, value: number) { /* ... */ }
86
+ }
87
+ ```
88
+
89
+ ## Dependency behavior
90
+
91
+ ### Required and optional dependencies
92
+
93
+ ```ts ignore-check
94
+ // Required: the plugin does not load while the service is absent.
95
+ export const inject = ['tools']
96
+
97
+ // Optional: omit inject and query with ctx.get() at the use site.
98
+ export function apply(ctx: Context) {
99
+ const metrics = ctx.get('metrics')
100
+ metrics?.record('plugin_loaded', 1)
101
+ }
102
+ ```
103
+
104
+ ### When a service disappears
105
+
106
+ If a required service disappears while the application is running, for example because its provider unloads:
107
+
108
+ 1. Dependent plugins dispose automatically.
109
+ 2. They load again when the service returns.
110
+
111
+ This prevents a plugin from calling a service that no longer exists.
112
+
113
+ ## Service isolation
114
+
115
+ `cordis.yml` can isolate services so separate plugin groups see separate instances of the same service:
116
+
117
+ ```yaml
118
+ - id: group-a
119
+ name: '@deepseek-ai/cordis-plugin-group'
120
+ group: true
121
+ isolate:
122
+ shell: true
123
+ config:
124
+ - name: '@deepseek-ai/dsh-bash-local'
125
+ config:
126
+ timeoutMs: 5000
127
+ - name: './src/plugin-a.ts'
128
+
129
+ - id: group-b
130
+ name: '@deepseek-ai/cordis-plugin-group'
131
+ group: true
132
+ isolate:
133
+ shell: true
134
+ config:
135
+ - name: '@deepseek-ai/dsh-bash-local'
136
+ config:
137
+ timeoutMs: 60000
138
+ - name: './src/plugin-b.ts'
139
+ ```
140
+
141
+ `plugin-a` and `plugin-b` each see the Bash instance in their own group, with no cross-group effect.
142
+
143
+ ## Built-in Harness services
144
+
145
+ The repository generates the service names, public methods, and source locations into each service's [subsystem page](../../reference/subsystems/core.md). Use those generated regions and the service's TypeScript interface while developing a plugin; do not maintain a second static list.
146
+
147
+ ## Next steps
148
+
149
+ - [Event system](./events.md) — communicate between plugins without tight coupling
150
+ - [Capability layering](../practice/index.md) — use services as capability interfaces
@@ -0,0 +1,157 @@
1
+ ---
2
+ editSource: "docs/user/develop/practice/index.md"
3
+ ---
4
+
5
+ # Three-role capability design
6
+
7
+ This page has two parts: a concept reference for the three-role capability pattern, followed by an advanced tutorial that builds one capability. Complete the [basic plugin path](../basic/index.md) and [services tutorial](../framework/service.md) first.
8
+
9
+ ## Concept reference
10
+
11
+ When a capability is general enough to need replaceable providers, such as Bash execution, Harness separates three roles: a **Service Definition**, a **Service Provider**, and a **Consumer**. Put the roles in separate packages when they need to evolve or be replaced independently; a package may otherwise own more than one role. The complete capability is its seam. No individual role is a seam.
12
+
13
+ ## Bash example
14
+
15
+ The Bash execution capability consists of:
16
+
17
+ - **Service Definition** (`dsh-shell`) — defines the Cordis service and Bash request and result types
18
+ - **Service Provider** (`dsh-bash-local`) — executes commands on the local machine
19
+ - **Consumer** (`dsh-tool-bash`) — exposes the capability as a model-callable tool
20
+
21
+ ```
22
+ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
23
+ │ dsh-shell │────▶│ dsh-bash-local │ │ dsh-tool-bash│
24
+ │(definition) │ │ (provider) │ │(consumer/tool)│
25
+ └─────────────┘ └──────────────────┘ └──────────────┘
26
+ ▲ │
27
+ └────────────────────────────────────────────┘
28
+ inject: ['shell']
29
+ ```
30
+
31
+ ## Benefits of the split
32
+
33
+ ### Replace providers
34
+
35
+ One Service Definition can have multiple providers selected through `cordis.yml`:
36
+
37
+ ```yaml
38
+ # Local execution
39
+ - name: '@deepseek-ai/dsh-bash-local'
40
+
41
+ # Replace this row with another package that provides the same service.
42
+ ```
43
+
44
+ The Service Definition and tool remain unchanged while the provider changes.
45
+
46
+ ### Evolve independently
47
+
48
+ - The Service Definition changes rarely after callers depend on its contract.
49
+ - Service Providers can improve performance and security independently.
50
+ - Consumers can change how they present the capability to the model.
51
+
52
+ ### Decouple dependencies
53
+
54
+ - The Service Provider depends on the Service Definition.
55
+ - The Consumer depends on the Service Definition.
56
+ - The Service Provider and Consumer **do not depend on each other**.
57
+
58
+ The [capability-seam reference](../../reference/capability-seams.md) owns the current built-in families and package links.
59
+
60
+ ## Tutorial: develop a three-role capability
61
+
62
+ ### Step 1: write the Service Definition
63
+
64
+ ```ts ignore-check
65
+ // packages/my-cap/my-cap/src/index.ts
66
+ import { Service, type Context } from '@deepseek-ai/cordis'
67
+
68
+ declare module '@deepseek-ai/cordis' {
69
+ interface Context {
70
+ myCap: MyCapService
71
+ }
72
+ }
73
+
74
+ export abstract class MyCapService extends Service {
75
+ constructor(ctx: Context) {
76
+ super(ctx, 'myCap')
77
+ }
78
+
79
+ /** Execute the capability. */
80
+ abstract execute(request: MyCapRequest): Promise<MyCapResult>
81
+ }
82
+
83
+ export interface MyCapRequest {
84
+ input: string
85
+ }
86
+
87
+ export interface MyCapResult {
88
+ output: string
89
+ }
90
+ ```
91
+
92
+ ### Step 2: write a Service Provider
93
+
94
+ ```ts ignore-check
95
+ // packages/my-cap/my-cap-local/src/index.ts
96
+ import type { Context } from '@deepseek-ai/cordis'
97
+ import { MyCapService, type MyCapRequest, type MyCapResult } from '@deepseek-ai/dsh-my-cap'
98
+
99
+ class MyCapLocal extends MyCapService {
100
+ async execute(request: MyCapRequest): Promise<MyCapResult> {
101
+ // Local provider behavior.
102
+ return { output: request.input.toUpperCase() }
103
+ }
104
+ }
105
+
106
+ export const name = 'my-cap-local'
107
+
108
+ export function apply(ctx: Context) {
109
+ ctx.plugin(MyCapLocal)
110
+ }
111
+ ```
112
+
113
+ ### Step 3: write a consumer
114
+
115
+ ```ts ignore-check
116
+ // packages/my-cap/tool-my-cap/src/index.ts
117
+ import type { Context } from '@deepseek-ai/cordis'
118
+ import { defineTool } from '@deepseek-ai/dsh-tools'
119
+
120
+ export const name = 'tool-my-cap'
121
+ export const inject = ['tools', 'myCap']
122
+
123
+ export function apply(ctx: Context) {
124
+ ctx.tools.register(defineTool({
125
+ name: 'my_cap',
126
+ description: 'Execute my capability.',
127
+ parameters: {
128
+ input: { type: 'string', required: true },
129
+ },
130
+ output: {
131
+ schema: { type: 'string' },
132
+ render: (_args, value) => [{ type: 'text', text: value }],
133
+ },
134
+ async execute(args) {
135
+ const result = await ctx.myCap.execute({ input: args.input })
136
+ return result.output
137
+ },
138
+ }))
139
+ }
140
+ ```
141
+
142
+ ### Compose them in cordis.yml
143
+
144
+ ```yaml
145
+ - name: '@deepseek-ai/dsh-my-cap-local'
146
+ - name: '@deepseek-ai/dsh-tool-my-cap'
147
+ ```
148
+
149
+ ## Design points
150
+
151
+ - **Do not split preemptively** — use separate packages only when the roles need to evolve independently. A simple tool plugin does not.
152
+ - **The Service Definition owns Request/Result types** — Service Providers and Consumers depend only on the Service Definition package.
153
+ - **Explicit > implicit** — resolve defaults in an explicit `resolve(request): Spec` step rather than hiding `?? default` expressions inside `run()`.
154
+
155
+ ## Next steps
156
+
157
+ - [LLM adapter](./llm-adapter.md) — implement an LLM provider
@@ -0,0 +1,190 @@
1
+ ---
2
+ editSource: "docs/user/develop/practice/llm-adapter.md"
3
+ ---
4
+
5
+ # LLM adapters
6
+
7
+ This guide connects a new LLM provider to Harness.
8
+
9
+ ## Overview
10
+
11
+ An LLM adapter extends `LlmAdapter` and implements `stream()`, translating Harness's provider-neutral request into a provider API call and translating the response back into Harness chunks.
12
+
13
+ ## Minimal implementation
14
+
15
+ ```ts
16
+ import type { Context } from '@deepseek-ai/cordis'
17
+ import Schema from '@deepseek-ai/schemastery'
18
+ import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
19
+
20
+ class MyAdapter extends LlmAdapter {
21
+ private apiKey: string
22
+
23
+ constructor(apiKey: string) {
24
+ super()
25
+ this.apiKey = apiKey
26
+ }
27
+
28
+ async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
29
+ // 1. Convert options.messages to the provider format.
30
+ // 2. Call the streaming API.
31
+ // 3. Convert the response into StreamChunk values.
32
+ }
33
+ }
34
+
35
+ export interface Config {
36
+ apiKey: string
37
+ providers: string[]
38
+ }
39
+
40
+ export const Config: Schema<Config> = Schema.object({
41
+ apiKey: Schema.string().required(),
42
+ providers: Schema.array(Schema.string()).required(),
43
+ })
44
+
45
+ export const name = 'my-llm-adapter'
46
+ export const inject = ['llm']
47
+
48
+ export function apply(ctx: Context, config: Config) {
49
+ const adapter = new MyAdapter(config.apiKey)
50
+ ctx.llm.registerAdapter(config.providers, adapter)
51
+ }
52
+ ```
53
+
54
+ ## StreamChunk protocol
55
+
56
+ `stream()` yields chunks using this protocol:
57
+
58
+ ```ts
59
+ import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
60
+
61
+ async function* exampleChunks(): AsyncIterable<StreamChunk> {
62
+ // 1. Start each content block with block-start.
63
+ yield { type: 'block-start', index: 0, blockType: 'text' }
64
+
65
+ // 2. Stream text through text-delta.
66
+ yield { type: 'text-delta', index: 0, text: 'Hello' }
67
+ yield { type: 'text-delta', index: 0, text: ' world' }
68
+
69
+ // 3. End each content block with block-end and the complete block.
70
+ yield {
71
+ type: 'block-end',
72
+ index: 0,
73
+ block: { type: 'text', text: 'Hello world' },
74
+ }
75
+
76
+ // 4. Tool-call block.
77
+ yield { type: 'block-start', index: 1, blockType: 'tool-call' }
78
+ yield {
79
+ type: 'tool-call-delta',
80
+ index: 1,
81
+ id: CallId('call-123'),
82
+ name: 'bash',
83
+ argumentsDelta: '{"command":"ls"}',
84
+ }
85
+ yield {
86
+ type: 'block-end',
87
+ index: 1,
88
+ block: {
89
+ type: 'tool-call',
90
+ id: CallId('call-123'),
91
+ name: 'bash',
92
+ arguments: '{"command":"ls"}',
93
+ },
94
+ }
95
+
96
+ // 5. Token usage.
97
+ yield { type: 'usage', usage: { inputTokens: 100, outputTokens: 50 } }
98
+
99
+ // 6. Finish reason.
100
+ yield { type: 'finish', reason: { kind: 'stop' } }
101
+ // Alternatively, { kind: 'tool-calls' } requests tool execution.
102
+ }
103
+ ```
104
+
105
+ ### Key rules
106
+
107
+ - Every `block-start` has a matching `block-end`.
108
+ - `index` increases from 0 and identifies content-block order.
109
+ - A `tool-call-delta` carries raw JSON text in `argumentsDelta`, either all at once or over multiple chunks.
110
+ - `finish` is the final chunk.
111
+ - Emit `usage` before `finish`.
112
+
113
+ ## GenerateOptions
114
+
115
+ `stream()` receives the exported `GenerateOptions` type. It includes the model, adapter-owned reasoning-effort id, conversation history, system prompt, tool schemas, generation parameters, stop sequences, and abort signal; treat the TypeScript type exported by `@deepseek-ai/dsh-llm` as authoritative. Map supported fields to the provider API. If the provider cannot honor a field, throw `LlmError` with a stable code instead of silently dropping it.
116
+
117
+ Override `resolveModel(provider, model, signal?)` to return exact provider/model identity plus optional `context` and `reasoning` metadata in one lookup. Reasoning metadata contains ordered opaque ids and display names plus an optional configured default; preserve the adapter's authoritative selectable list, including `off` when its upstream capability API returns it, instead of promoting those values into a core enum. Honor the optional signal for asynchronous lookup so cancellation and disposal reach quiescence. The service validates the aggregate and rejects unsupported explicit efforts before `stream()`; omitting `reasoning` means that model has no selectable reasoning-effort capability.
118
+
119
+ ## Register an adapter
120
+
121
+ ```ts ignore-check
122
+ ctx.llm.registerAdapter(['my-provider'], adapter)
123
+ ```
124
+
125
+ The first argument lists provider routes handled by the adapter. `GenerateOptions.provider` selects the registered adapter, while `GenerateOptions.model` passes an adapter-owned model id without lifecycle registration. Override `listModels()` when the adapter can advertise model choices to selectors.
126
+
127
+ ## Use it from cordis.yml
128
+
129
+ ```yaml
130
+ - id: my-llm
131
+ name: './src/my-llm-adapter.ts'
132
+ config:
133
+ apiKey: !!js process.env.MY_API_KEY
134
+ providers:
135
+ - my-provider
136
+
137
+ - id: agent-loop
138
+ name: '@deepseek-ai/dsh-agent-loop'
139
+ config:
140
+ agents:
141
+ - id: main
142
+ provider: my-provider
143
+ model: my-model-v1
144
+ ```
145
+
146
+ ## Reference implementations
147
+
148
+ The repository contains complete implementations:
149
+
150
+ - `packages/llm/llm-deepseek/` — DeepSeek API adapter using the OpenAI-compatible format
151
+ - `packages/llm/llm-pi-ai/` — Pi AI adapter using a different API format
152
+
153
+ Compare the two shipped adapters to see the same harness contract implemented over different provider SDKs.
154
+
155
+ ## Error handling
156
+
157
+ Adapters throw transport and protocol failures as `LlmError` values with stable codes. The agent loop preserves the error and code for diagnostics and policy; it does not convert an ordinary `Error` automatically. Every provider HTTP request must also merge `attributionHeaders()` and forward `options.signal`.
158
+
159
+ ```ts
160
+ import {
161
+ attributionHeaders,
162
+ LlmAdapter,
163
+ LlmError,
164
+ type GenerateOptions,
165
+ type StreamChunk,
166
+ } from '@deepseek-ai/dsh-llm'
167
+
168
+ class HttpAdapter extends LlmAdapter {
169
+ constructor(private readonly endpoint: string) {
170
+ super()
171
+ }
172
+
173
+ async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
174
+ const response = await fetch(this.endpoint, {
175
+ method: 'POST',
176
+ headers: {
177
+ 'content-type': 'application/json',
178
+ ...attributionHeaders(),
179
+ },
180
+ body: JSON.stringify({ model: options.model, messages: options.messages }),
181
+ ...options.signal ? { signal: options.signal } : {},
182
+ })
183
+ if (!response.ok) {
184
+ throw new LlmError(`Provider API error: ${response.status}`, 'PROVIDER_HTTP_ERROR')
185
+ }
186
+ // A real adapter parses the response and emits the complete chunk sequence.
187
+ yield { type: 'finish', reason: { kind: 'stop' } }
188
+ }
189
+ }
190
+ ```