DXOS Skills
Skills define AI toolkits for a domain (e.g. markdown, kanban). They combine operation definitions (for tool schemas) with operation handlers (for runtime execution). See the operations skill (.cursor/skills/operations/SKILL.md) for defining operations.
Skill definition
A skill has three parts:
| Field | Type | Purpose |
|---|---|---|
key |
string |
Globally unique key (reverse-domain style). |
operations |
OperationHandlerSet.OperationHandlerSet |
Handler set for runtime invocation. |
make |
() => Skill.Skill |
Factory that creates the Skill instance with tools. |
Example (see packages/plugins/plugin-markdown/src/skills/markdown-skill.ts):
import { type AppCapabilities } from '@dxos/app-toolkit';
import { Skill, Template } from '@dxos/compute';
import { trim } from '@dxos/util';
import { MarkdownHandlers, Create, Open, Update } from './operations';
const SKILL_KEY = 'org.dxos.skill.markdown';
const make = () =>
Skill.make({
key: SKILL_KEY,
name: 'Markdown',
tools: Skill.toolDefinitions({ operations: [Create, Open, Update] }),
instructions: Template.make({
source: trim`
You can create, read and update markdown documents.
When asked to edit or update documents return updates as a set of compact diff string pairs.
`,
}),
});
const skill: AppCapabilities.SkillDefinition = {
key: SKILL_KEY,
operations: MarkdownHandlers,
make,
};
export default skill;
- Definitions (
Create,Open,Update) go toSkill.toolDefinitions({ operations })— they provide schemas for the AI. - Handlers (
MarkdownHandlers) go to the skill'soperationsfield — they are invoked at runtime.
File structure
Skills live inside plugins. Structure:
plugin-my-domain/
├── src/
│ ├── skills/
│ │ ├── index.ts # Re-exports skill
│ │ ├── my-skill.ts # Skill definition (key, operations, make)
│ │ └── operations/ # Operations (definitions + handlers)
│ │ ├── definitions.ts
│ │ ├── create.ts
│ │ ├── open.ts
│ │ ├── update.ts
│ │ └── index.ts
│ └── capabilities/
│ └── skill-definition/
│ ├── index.ts # Lazy export
│ └── skill-definition.ts # Contributes to AppCapabilities.SkillDefinition
Where skills are registered (Composer)
- Capability module — contributes the skill to the app:
// capabilities/skill-definition/skill-definition.ts
import * as Effect from 'effect/Effect';
import { Capability } from '@dxos/app-framework';
import { AppCapabilities } from '@dxos/app-toolkit';
import { MarkdownSkill } from '../../skills';
const skillDefinition = Capability.makeModule<[], Capability.Capability<typeof AppCapabilities.SkillDefinition>[]>(() =>
Effect.succeed([Capability.contribute(AppCapabilities.SkillDefinition, MarkdownSkill)]),
);
export default skillDefinition;
- Plugin — registers the capability module:
// MarkdownPlugin.tsx
import { SkillDefinition } from './capabilities';
export const MarkdownPlugin = Plugin.define(meta).pipe(
AppPlugin.addSkillDefinitionModule({ activate: SkillDefinition }),
// ...other modules
Plugin.make,
);
- Composer — plugin must be in the app's plugin list (
packages/apps/composer-app/src/plugin-defs.tsx). If the plugin is imported there, its skills are available.
Testing with AssistantTestLayer
Use AssistantTestLayer from @dxos/assistant/testing to test operations and AI flows that use skills. Operation definitions and OperationHandlerSet wiring follow the same patterns as production code (see .cursor/skills/operations/SKILL.md).
import { AssistantTestLayer } from '@dxos/assistant/testing';
import { Skill } from '@dxos/compute';
import MarkdownSkill from '../markdown-skill';
const TestLayer = AssistantTestLayer({
operationHandlers: MarkdownSkill.operations,
types: [SpaceProperties, Collection.Collection, Skill.Skill, Markdown.Document, HasSubject.HasSubject],
tracing: 'pretty',
});
operationHandlers— the skill'soperations(OperationHandlerSet.OperationHandlerSet). Required so the runtime can resolve handlers when you callOperation.invoke/Operation.Service(same mechanism as in-app operation execution).types— ECHO types the test needs (e.g.Markdown.Document,Skill.Skill). Mirror anytypesdeclared on yourOperation.makedefinitions where relevant.skills— optional; use when the test binds skills viaAiContextService.bindContext({ skills: [...] })and you need the registry to know about them.
To invoke an operation directly, use Operation.invoke from @dxos/operation (not FunctionInvocationService from @dxos/functions, which is deprecated):
import { Operation } from '@dxos/operation';
import { Create } from './definitions';
const result =
yield *
Operation.invoke(Create, {
name: 'My Doc',
content: 'Hello world.',
});
Inside another operation's handler, the same API applies (yield* Operation.invoke(...), yield* Operation.schedule(...)); see the operations skill section Invoking Operations.